Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@fimbul/mimir

Package Overview
Dependencies
Maintainers
2
Versions
125
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fimbul/mimir - npm Package Compare versions

Comparing version 0.21.0-dev.20190322 to 0.21.0-dev.20190329

4

package.json
{
"name": "@fimbul/mimir",
"version": "0.21.0-dev.20190322",
"version": "0.21.0-dev.20190329",
"description": "Core rules of the Fimbullinter project",

@@ -28,3 +28,3 @@ "main": "recommended.yaml",

"dependencies": {
"@fimbul/ymir": "0.21.0-dev.20190322",
"@fimbul/ymir": "0.21.0-dev.20190329",
"chalk": "^2.3.2",

@@ -31,0 +31,0 @@ "debug": "^4.0.0",

@@ -40,3 +40,3 @@ "use strict";

if (line !== 0 || character === 0 || !summary.content.startsWith('\uFEFF'))
character += 1;
character += 1; // avoid incrementing the character position on the first line if BOM is present, editors ignore BOM
const position = `${line + 1}:${character}`;

@@ -86,3 +86,3 @@ if (position.length > this.maxPositionWidth)

? undefined
: lines.slice(1).join('\n');
: lines.slice(1).join('\n'); // remove first line, because it's always empty
}

@@ -89,0 +89,0 @@ }

@@ -5,6 +5,15 @@ import { TypedRule } from '@fimbul/ymir';

private maybePromiseLike;
/**
* A type is thenable when it has a callable `then` property.
* We don't care if the signatures are actually callable because the compiler already complains about that.
*/
private isThenable;
private isAsyncIterable;
/**
* We consider a type as AsyncIterable when it has a property key [Symbol.asyncIterator].
* The spec requires this property to be a function returning an object with a `next` method which returns a Promise of IteratorResult.
* But that's none of our business as the compiler already does the heavy lifting.
*/
private hasSymbolAsyncIterator;
private isIterableOfPromises;
}

@@ -41,2 +41,6 @@ "use strict";

}
/**
* A type is thenable when it has a callable `then` property.
* We don't care if the signatures are actually callable because the compiler already complains about that.
*/
isThenable(type, node) {

@@ -51,2 +55,3 @@ const then = type.getProperty('then');

for (const t of tsutils_1.unionTypeParts(type))
// there must either be a `AsyncIterable` or `Iterable<PromiseLike<any>>`
if (this.hasSymbolAsyncIterator(t) || this.isIterableOfPromises(t, node))

@@ -56,2 +61,7 @@ return true;

}
/**
* We consider a type as AsyncIterable when it has a property key [Symbol.asyncIterator].
* The spec requires this property to be a function returning an object with a `next` method which returns a Promise of IteratorResult.
* But that's none of our business as the compiler already does the heavy lifting.
*/
hasSymbolAsyncIterator(type) {

@@ -58,0 +68,0 @@ return type.getProperties().some((prop) => prop.escapedName === '__@asyncIterator');

@@ -60,2 +60,3 @@ "use strict";

function isWhitelisted(name) {
// exclude all identifiers that start with an uppercase letter to allow `new Event()` and stuff
if (name.charAt(0) === name.charAt(0).toUpperCase())

@@ -62,0 +63,0 @@ return true;

@@ -16,9 +16,9 @@ "use strict";

iterate(wrap, end) {
do {
do { // iterate as linked list until we find a generator
if (wrap.kind === ts.SyntaxKind.Block && isGenerator(wrap.node.parent)) {
this.containsYield = false;
wrap.children.forEach(this.visitNode, this);
if (this.shouldFail())
wrap.children.forEach(this.visitNode, this); // walk the function body recursively
if (this.shouldFail()) // call as method so CFA doesn't infer `this.containsYield` as always false
this.fail(wrap.node.parent);
wrap = wrap.skip;
wrap = wrap.skip; // continue right after the function body
}

@@ -39,2 +39,3 @@ else {

const saved = this.containsYield;
// can iterate as linked list again for nested functions
this.iterate(wrap.next, wrap.skip);

@@ -41,0 +42,0 @@ this.containsYield = saved;

@@ -36,2 +36,3 @@ "use strict";

default:
// union of literal types, do not add these to `valuesSeen`, but display an error if all literals were already seen
if (literals.every((v) => valuesSeen.has(v)))

@@ -59,3 +60,3 @@ this.addFindingAtNode(clause.expression, `Duplicate 'case ${literals.sort().join(' | ')}'.`);

if (node.kind === ts.SyntaxKind.NullKeyword)
return [formatPrimitive(prefixFn(null))];
return [formatPrimitive(prefixFn(null))]; // tslint:disable-line:no-null-keyword
if (tsutils_1.isIdentifier(node) && node.originalKeywordKind === ts.SyntaxKind.UndefinedKeyword)

@@ -74,2 +75,3 @@ return [formatPrimitive(prefixFn(undefined))];

for (const t of tsutils_1.unionTypeParts(type)) {
// TODO handle intersection types
if (tsutils_1.isLiteralType(t)) {

@@ -85,3 +87,3 @@ result.add(formatPrimitive(prefixFn(t.value)));

else if (t.flags & ts.TypeFlags.Null) {
result.add(formatPrimitive(prefixFn(null)));
result.add(formatPrimitive(prefixFn(null))); // tslint:disable-line:no-null-keyword
}

@@ -107,2 +109,3 @@ else {

case ts.SyntaxKind.MinusToken:
// there's no '-0n'
return (v) => isBigInt(v) ? next(Object.assign({}, v, { negative: !v.negative && v.base10Value !== '0' })) : next(-v);

@@ -123,2 +126,3 @@ case ts.SyntaxKind.TildeToken:

if (v.negative) {
// negative values become positive and get decremented by 1
for (let i = digits.length - 1; i >= 0; --i) {

@@ -128,2 +132,3 @@ const current = +digits[i] - 1;

if (current === 0 && i === 0 && digits.length !== 1) {
// remove leading zero
digits.shift();

@@ -140,2 +145,3 @@ }

else {
// positive values are incremented by one and become negative
for (let i = digits.length - 1; i >= 0; --i) {

@@ -142,0 +148,0 @@ const current = +digits[i] + 1;

@@ -26,2 +26,3 @@ "use strict";

checkObject({ properties }) {
/** key: propertyName, value: isAccessor */
const propertiesSeen = new Map();

@@ -39,3 +40,3 @@ for (let i = properties.length - 1; i >= 0; --i) {

if (isAccessor)
continue;
continue; // avoid overriding the isAccessor state
}

@@ -42,0 +43,0 @@ }

import { TypedRule } from '@fimbul/ymir';
export declare class Rule extends TypedRule {
apply(): void;
/**
* This function is necessary because higher order function type inference creates Signatures whose declaration has no type parameters.
* @see https://github.com/Microsoft/TypeScript/issues/30296
*
* To work around this, we look for a single call signature on the called expression and use its type parameters instead.
* As a bonus this also works for unified signatures from union types.
*/
private getTypeParametersOfCallSignature;

@@ -5,0 +12,0 @@ private mapTypeParameter;

@@ -26,2 +26,9 @@ "use strict";

}
/**
* This function is necessary because higher order function type inference creates Signatures whose declaration has no type parameters.
* @see https://github.com/Microsoft/TypeScript/issues/30296
*
* To work around this, we look for a single call signature on the called expression and use its type parameters instead.
* As a bonus this also works for unified signatures from union types.
*/
getTypeParametersOfCallSignature(node) {

@@ -40,2 +47,5 @@ let expr;

const signatures = this.checker.getTypeAtLocation(expr).getCallSignatures();
// abort if not all signatures have type parameters:
// higher order function type inference only works for a single call signature
// call signature unification puts type parameters on every resulting signature
if (signatures.length === 0 || signatures.some((s) => s.typeParameters === undefined))

@@ -46,2 +56,3 @@ return [];

mapTypeParameter(type) {
// fall back to NodeBuilder for renamed TypeParameters, they have no declaration and therefore we cannot directly access the default
return type.symbol.declarations === undefined

@@ -67,11 +78,14 @@ ? mapTypeParameterDeclaration(this.checker.typeParameterToDeclaration(type, undefined, ts.NodeBuilderFlags.IgnoreErrors))

if (signature.declaration !== undefined) {
// There is an explicitly declared construct signature
const typeParameters = ts.getEffectiveTypeParameterDeclarations(signature.declaration);
if (typeParameters.length !== 0)
if (typeParameters.length !== 0) // only check the signature if it declares type parameters
return this.checkInferredTypeParameters(signature, typeParameters.map(mapTypeParameterDeclaration), node);
if (signature.declaration.kind !== ts.SyntaxKind.Constructor)
return;
return; // don't look for type parameters on non-class parents
}
// Otherwise look up the TypeParameters of the ClassDeclaration
const { symbol } = this.checker.getTypeAtLocation(node.expression);
if (symbol === undefined || symbol.declarations === undefined)
return;
// collect all TypeParameters and their defaults from all merged declarations
const mergedTypeParameters = [];

@@ -92,3 +106,3 @@ for (const declaration of symbol.declarations) {

if (typeParameters.every((t) => t.hasDefault))
return;
return; // nothing to do here if every type parameter as a default
const typeArguments = this.checker.signatureToSignatureDeclaration(signature, ts.SyntaxKind.CallExpression, undefined, ts.NodeBuilderFlags.WriteTypeArgumentsOfSignature | ts.NodeBuilderFlags.IgnoreErrors).typeArguments;

@@ -106,3 +120,3 @@ for (let i = 0; i < typeParameters.length; ++i) {

ymir_1.excludeDeclarationFiles,
ymir_1.typescriptOnly
ymir_1.typescriptOnly // in .js files TypeParameters default to `any`
], Rule);

@@ -109,0 +123,0 @@ exports.Rule = Rule;

@@ -26,2 +26,3 @@ "use strict";

return;
// if expression is a type variable, the type checker already handles everything as expected
const originalTypeParts = getLiteralsByType(this.checker.getTypeAtLocation(node.expression));

@@ -81,2 +82,6 @@ if (isEmpty(originalTypeParts))

};
// typically literal types are swallowed by their corresponding widened type if they occur in the same union
// this is not the case with intersections: `(string & {foo: string}) | ('bar' & {bar: string})`
// therefore we need to reset all previously seen literal types if we see the widened type
// we also need to remember not to store any new literal types of that kind
let seenString = false;

@@ -83,0 +88,0 @@ let seenNumber = false;

@@ -12,3 +12,3 @@ "use strict";

for (let match = re.exec(this.sourceFile.text); match !== null; match = re.exec(this.sourceFile.text)) {
if (match[1].length & 1)
if (match[1].length & 1) // only check if backslash is not escaped
continue;

@@ -15,0 +15,0 @@ const { node } = tsutils_1.getWrappedNodeAtPosition(wrappedAst || (wrappedAst = this.context.getWrappedAst()), match.index);

@@ -168,2 +168,3 @@ "use strict";

case ts.SyntaxKind.Decorator:
// skip the declaration the decorator belongs to, because decorators are always applied in the outer context
node = node.parent.parent;

@@ -170,0 +171,0 @@ break;

@@ -62,7 +62,13 @@ "use strict";

if (tsutils_1.isFunctionScopeBoundary(node))
return false;
return false; // stop at function boundaries
if (tsutils_1.isTryStatement(node.parent)) {
if (node.parent.tryBlock === node ||
if (
// statements inside the try block always have an error handler, either catch or finally
node.parent.tryBlock === node ||
// return await inside the catch block is allowed if there is a finally block
// otherwise the finally block is executed before the promise returned from catch resolves
node.parent.finallyBlock !== undefined && node.parent.catchClause === node)
return true;
// we know we can skip over the TryStatement, it always has a parent
// if it's a function or the SourceFile, we stop. or we may potentially land in another (try/catch) block
node = node.parent.parent;

@@ -69,0 +75,0 @@ }

@@ -80,2 +80,3 @@ "use strict";

case ts.SyntaxKind.DoStatement:
// statements after loops, that go on forever without breaking, are never executed
return getConstantIterationCondition(statement) === true &&

@@ -82,0 +83,0 @@ !tsutils_1.getControlFlowEnd(statement.statement).statements.some((jump) => jump.kind === ts.SyntaxKind.BreakStatement && (jump.label === undefined || labels.includes(jump.label.text)));

@@ -12,2 +12,3 @@ "use strict";

for (const node of this.context.getFlatAst()) {
// TODO maybe check Type["property"]
if (tsutils_1.isIdentifier(node)) {

@@ -164,2 +165,3 @@ if (shouldCheckIdentifier(node))

case ts.SyntaxKind.CallExpression:
// note: NewExpression will never get here, because if the class is deprecated, we show an error all the time
return parent.expression === node;

@@ -184,3 +186,3 @@ case ts.SyntaxKind.JsxOpeningElement:

return false;
case ts.SyntaxKind.ShorthandPropertyAssignment:
case ts.SyntaxKind.ShorthandPropertyAssignment: // checked separately
return node.parent.name !== node;

@@ -192,4 +194,7 @@ default:

function shouldCheckQualifiedName(node) {
// if parent is a QualifiedName, it is the my.ns part of my.ns.Something -> we definitely want to check that
// if the parent is an ImportEqualsDeclaration -> we don't want to check the rightmost identifier, because importing is not that bad
// everything else is a TypeReference -> we want to check that
return node.parent.kind !== ts.SyntaxKind.ImportEqualsDeclaration;
}
//# sourceMappingURL=no-unstable-api-use.js.map

@@ -20,2 +20,3 @@ "use strict";

else if (tsutils_1.isExpressionStatement(node)) {
// allow `void asyncFn()` to mute 'await-async-result'
if (!isDirective(node) && node.expression.kind !== ts.SyntaxKind.VoidExpression)

@@ -45,3 +46,3 @@ this.checkNode(node.expression, node);

case ts.SyntaxKind.DeleteExpression:
case ts.SyntaxKind.PostfixUnaryExpression:
case ts.SyntaxKind.PostfixUnaryExpression: // i++, i--
return true;

@@ -89,2 +90,6 @@ case ts.SyntaxKind.PrefixUnaryExpression:

exports.Rule = Rule;
/**
* A directive is an expression statement containing only a string literal.
* Directives need to be located at the beginning of a function block and be consecutive.
*/
function isDirective(statement) {

@@ -102,2 +107,3 @@ if (statement.expression.kind !== ts.SyntaxKind.StringLiteral)

}
/** `void 0` and `void(0)` are allowed to have no side effect. */
function isAllowedVoidExpression(expr) {

@@ -108,2 +114,3 @@ if (expr.kind === ts.SyntaxKind.ParenthesizedExpression)

}
/** Allow `(0, eval)('foo')` */
function isIndirectEval(node) {

@@ -110,0 +117,0 @@ return tsutils_1.isIdentifier(node.right) && node.right.text === 'eval' &&

@@ -10,3 +10,4 @@ import { TypedRule } from '@fimbul/ymir';

private reportUselessTypeAssertion;
/** Returns the contextual type if it is a position that does not contribute to control flow analysis. */
private getSafeContextualType;
}

@@ -36,13 +36,17 @@ "use strict";

checkDefiniteAssignmentAssertion(node) {
// compiler already emits an error for definite assignment assertions on ambient or initialized variables
if (node.exclamationToken !== undefined &&
node.initializer === undefined && (!tsutils_1.isStrictCompilerOptionEnabled(this.context.compilerOptions, 'strictNullChecks') ||
getNullableFlags(this.checker.getTypeAtLocation(node.name), true) & ts.TypeFlags.Undefined))
getNullableFlags(this.checker.getTypeAtLocation(node.name), true) & ts.TypeFlags.Undefined // type does not allow undefined
))
this.addFinding(node.exclamationToken.end - 1, node.exclamationToken.end, FAIL_DEFINITE_ASSIGNMENT, ymir_1.Replacement.delete(node.exclamationToken.pos, node.exclamationToken.end));
}
checkDefiniteAssignmentAssertionProperty(node) {
// compiler emits an error for definite assignment assertions on ambient, initialized or abstract properties
if (node.exclamationToken !== undefined &&
node.initializer === undefined &&
!tsutils_1.hasModifier(node.modifiers, ts.SyntaxKind.AbstractKeyword) && (node.name.kind !== ts.SyntaxKind.Identifier ||
!tsutils_1.hasModifier(node.modifiers, ts.SyntaxKind.AbstractKeyword) && (node.name.kind !== ts.SyntaxKind.Identifier || // properties with string or computed name are not checked
!tsutils_1.isStrictCompilerOptionEnabled(this.context.compilerOptions, 'strictPropertyInitialization') ||
getNullableFlags(this.checker.getTypeAtLocation(node), true) & ts.TypeFlags.Undefined))
getNullableFlags(this.checker.getTypeAtLocation(node), true) & ts.TypeFlags.Undefined // type does not allow undefined
))
this.addFinding(node.exclamationToken.end - 1, node.exclamationToken.end, FAIL_DEFINITE_ASSIGNMENT, ymir_1.Replacement.delete(node.exclamationToken.pos, node.exclamationToken.end));

@@ -55,3 +59,3 @@ }

const flags = getNullableFlags(this.checker.getBaseConstraintOfType(originalType) || originalType);
if (flags !== 0) {
if (flags !== 0) { // type is nullable
const contextualType = this.getSafeContextualType(node);

@@ -76,3 +80,3 @@ if (contextualType === undefined || (flags & ~getNullableFlags(contextualType, true)))

let targetType = this.checker.getTypeFromTypeNode(node.type);
if ((targetType.flags & ts.TypeFlags.Literal) !== 0 && !tsutils_1.isInConstContext(node) ||
if ((targetType.flags & ts.TypeFlags.Literal) !== 0 && !tsutils_1.isInConstContext(node) || // allow "foo" as "foo" to avoid widening
tsutils_1.isObjectType(targetType) && (targetType.objectFlags & ts.ObjectFlags.Tuple || couldBeTupleType(targetType)))

@@ -88,6 +92,9 @@ return;

const contextualType = this.getSafeContextualType(node);
// TODO use assignability check once it's exposed from TypeChecker
if (contextualType === undefined ||
contextualType.flags & (ts.TypeFlags.TypeVariable | ts.TypeFlags.Instantiable) ||
(contextualType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) === 0 &&
// contextual type is exactly the same
!utils_1.typesAreEqual(sourceType, contextualType, this.checker) &&
// contextual type is an optional parameter or similar
!utils_1.typesAreEqual(sourceType, tsutils_1.removeOptionalityFromType(this.checker, contextualType), this.checker))

@@ -111,4 +118,7 @@ return;

}
/** Returns the contextual type if it is a position that does not contribute to control flow analysis. */
getSafeContextualType(node) {
const parent = node.parent;
// If assertion is used as argument, check if the function accepts this expression without an assertion
// TODO expand this to VariableLike initializers and return expressions where a type declaration exists
switch (parent.kind) {

@@ -120,3 +130,3 @@ case ts.SyntaxKind.CallExpression:

break;
case ts.SyntaxKind.TemplateSpan:
case ts.SyntaxKind.TemplateSpan: // TODO return 'any' for non-tagged template expressions
case ts.SyntaxKind.JsxExpression:

@@ -148,2 +158,19 @@ break;

}
/**
* Determine if the assertion may be necessary to avoid a compile error for use before being assigned.
* This function may yield some false negatives, but is needed until https://github.com/Microsoft/TypeScript/issues/20221 is implemented.
*
* The rules are as follows:
* * strictNullChecks are enabled
* * assertion is on an identifier
* * identifier is a mutable variable
* * no destructuring, parameters, catch binding, ambient declarations
* * variable is not initialized
* * variable has no definite assignment assertion (exclamation mark)
* * variable has a type annotation
* * declared type is equal to the type at the assertion
* * declaration and assertion are in the same function scope
*
* We don't need to worry about strictPropertyInitialization errors, because they cannot be suppressed with a non-null assertion
*/
function maybeUsedBeforeBeingAssigned(node, type, checker) {

@@ -167,5 +194,7 @@ if (node.kind !== ts.SyntaxKind.Identifier || getNullableFlags(type, true) & ts.TypeFlags.Undefined)

while (useFunctionScope !== declaringFunctionScope && isInlinedIife(useFunctionScope))
// TypeScript inlines IIFEs, so we need to do as well
useFunctionScope = findupFunction(useFunctionScope.parent.parent);
return useFunctionScope === declaringFunctionScope;
}
/** Finds the nearest parent that has a function scope. */
function findupFunction(node) {

@@ -178,6 +207,10 @@ while (!tsutils_1.isFunctionScopeBoundary(node) && node.kind !== ts.SyntaxKind.SourceFile)

return (tsutils_1.isFunctionExpression(node) || tsutils_1.isArrowFunction(node)) &&
node.asteriskToken === undefined &&
!tsutils_1.hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword) &&
node.asteriskToken === undefined && // exclude generators
!tsutils_1.hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword) && // exclude async functions
tsutils_1.getIIFE(node) !== undefined;
}
/**
* Sometimes tuple types don't have ObjectFlags.Tuple set, like when they're being matched against an inferred type.
* So, in addition, check if there are integer properties 0..n and no other numeric keys
*/
function couldBeTupleType(type) {

@@ -192,2 +225,3 @@ const properties = type.getProperties();

if (i === 0)
// if there are no integer properties, this is not a tuple
return false;

@@ -194,0 +228,0 @@ break;

@@ -38,2 +38,3 @@ "use strict";

case ts.SyntaxKind.EnumDeclaration:
// allow 'declare const enum' in declaration files, because it's required in declaration files
return !this.sourceFile.isDeclarationFile && tsutils_1.hasModifier(node.modifiers, ts.SyntaxKind.ConstKeyword);

@@ -40,0 +41,0 @@ default:

@@ -34,3 +34,3 @@ "use strict";

if (isRestProperty(properties[properties.length - 1]))
return;
return; // don't report empty property destructuring if there is a rest property as it's used to exclude properties
for (let i = 0;; ++i) {

@@ -40,3 +40,5 @@ const isLast = i === properties.length - 1;

if (!isUsed(property))
this.addFindingAtNode(property, "Destructuring property is not necessary as it doesn't assign to a variable.", ymir_1.Replacement.delete(property.pos, isLast ? properties.end : properties[i + 1].pos));
this.addFindingAtNode(property, "Destructuring property is not necessary as it doesn't assign to a variable.",
// make sure to delete the next comma to avoid consecutive commas
ymir_1.Replacement.delete(property.pos, isLast ? properties.end : properties[i + 1].pos));
if (isLast)

@@ -99,5 +101,5 @@ break;

switch (node.kind) {
case ts.SyntaxKind.OmittedExpression:
case ts.SyntaxKind.OmittedExpression: // only possible in array destructuring
return false;
case ts.SyntaxKind.SpreadElement:
case ts.SyntaxKind.SpreadElement: // only possible in array destructuring
return isNonEmptyAssignmentTarget(node.expression);

@@ -104,0 +106,0 @@ case ts.SyntaxKind.ObjectLiteralExpression:

@@ -75,2 +75,5 @@ "use strict";

continue;
// TODO we currently cannot autofix this case: it's possible to use a default value that's not assignable to the
// destructured type. The type of the variable then includes the type of the initializer as well.
// Removing the initializer might also remove its type from the union type causing type errors elsewhere.
this.addFindingAtNode(element.initializer, "Unnecessary default value as this property is never 'undefined'.");

@@ -77,0 +80,0 @@ }

@@ -32,15 +32,16 @@ "use strict";

if (tsutils_1.isIterationStatement(parent) || isBreak && parent.kind === ts.SyntaxKind.SwitchStatement) {
// we found the closest jump target. now we need to check if it has the same label as the jump statement
parent = parent.parent;
while (tsutils_1.isLabeledStatement(parent)) {
if (parent.label.text === label.text)
return false;
return false; // label is present on the closest jump target
parent = parent.parent;
}
return true;
return true; // label is not present on the closest jump target
}
if (isBreak && tsutils_1.isLabeledStatement(parent) && parent.label.text === label.text)
return true;
return true; // label is not on an IterationStatement or SwitchStatement -> breaking out of blocks always requires label
} while (!tsutils_1.isFunctionScopeBoundary(parent) && parent.kind !== ts.SyntaxKind.SourceFile);
return true;
return true; // label is not in scope, should never get here in correct code
}
//# sourceMappingURL=no-useless-jump-label.js.map

@@ -15,2 +15,7 @@ import { TypedRule } from '@fimbul/ymir';

private executePredicate;
/**
* If one of the types in the intersection matches the precicate, this function returns true.
* Otherwise if any of the types results in undefined, this function returns undefined.
* If every type results in false, it returns false.
*/
private matchIntersectionType;

@@ -17,0 +22,0 @@ private getTypeOfExpression;

@@ -115,3 +115,3 @@ "use strict";

if (tsutils_1.isBinaryExpression(node)) {
if (isEqualityOperator(node.operatorToken.kind))
if (isEqualityOperator(node.operatorToken.kind)) // TODO check <, >, <=, >= with literal types
return nested ? undefined : this.isConstantComparison(node.left, node.right, node.operatorToken.kind);

@@ -127,2 +127,3 @@ if (node.operatorToken.kind === ts.SyntaxKind.InKeyword)

}
// in non-strictNullChecks mode we can only detect if a type is definitely falsy
return this.executePredicate(this.getTypeOfExpression(node), this.strictNullChecks ? truthyFalsy : falsy);

@@ -189,2 +190,3 @@ }

getPrimitiveLiteral(node) {
// TODO reuse some logic from 'no-duplicate-case' to compute prefix unary expressions
let type = this.getTypeOfExpression(node);

@@ -207,3 +209,6 @@ type = this.checker.getBaseConstraintOfType(type) || type;

return;
const result = this.executePredicate(type, predicate.nullable ? predicate.check : (t) => tsutils_1.isEmptyObjectType(t) ? undefined : predicate.check(t));
const result = this.executePredicate(type,
// empty object type can contain anything but `null | undefined`
// TODO use assignability check to avoid false positives
predicate.nullable ? predicate.check : (t) => tsutils_1.isEmptyObjectType(t) ? undefined : predicate.check(t));
return result && !this.strictNullChecks

@@ -241,2 +246,7 @@ ? undefined

}
/**
* If one of the types in the intersection matches the precicate, this function returns true.
* Otherwise if any of the types results in undefined, this function returns undefined.
* If every type results in false, it returns false.
*/
matchIntersectionType(type, predicate) {

@@ -279,3 +289,3 @@ let result = false;

return;
const names = utils_1.lateBoundPropertyNames(name, this.checker);
const names = utils_1.lateBoundPropertyNames(name, this.checker); // TODO lateBoundPropertyNames should also be aware of index signatures
if (!names.known)

@@ -285,6 +295,7 @@ return;

for (const { symbolName } of names.properties) {
// we check every union type member separately because symbols lose optionality when accessed through union types
for (const type of types) {
const symbol = utils_1.getPropertyOfType(type, symbolName);
if (symbol === undefined || symbol.flags & ts.SymbolFlags.Optional)
return;
return; // it might be present at runtime, so we don't return false
}

@@ -311,2 +322,3 @@ }

}
// TODO use assignability check
return tsutils_1.isEmptyObjectType(type) ? undefined : true;

@@ -337,2 +349,3 @@ }

return true;
// check if this could be the global `Function` type
return type.symbol !== undefined && type.symbol.escapedName === 'Function' &&

@@ -339,0 +352,0 @@ hasPropertyOfKind(type, 'apply', ts.SymbolFlags.Method) &&

@@ -61,2 +61,4 @@ "use strict";

default:
// TODO do something special for MethodDeclaration?
// exclude accessors
return false;

@@ -71,3 +73,3 @@ }

for (const property of properties) {
fix.push(ymir_1.Replacement.replace(prevEnd, property.pos, ' '));
fix.push(ymir_1.Replacement.replace(prevEnd, property.pos, ' ')); // use space instead of comma as separator
switch (property.kind) {

@@ -90,2 +92,3 @@ case ts.SyntaxKind.SpreadAssignment:

if (elements.length === 0) {
// handle special case of empty object or array to remove trailing comma
const parent = node.parent;

@@ -92,0 +95,0 @@ const containingList = parent.kind === ts.SyntaxKind.ArrayLiteralExpression

@@ -12,5 +12,5 @@ "use strict";

this.strictFile = this.context.compilerOptions !== undefined && tsutils_1.isStrictCompilerOptionEnabled(this.context.compilerOptions, 'alwaysStrict')
? "due to the compilerOption 'alwaysStrict' this code is in strict mode"
? "due to the compilerOption 'alwaysStrict' this code is in strict mode" /* Option */
: ts.isExternalModule(this.sourceFile)
? "ES6 modules are always in strict mode"
? "ES6 modules are always in strict mode" /* Module */
: undefined;

@@ -33,3 +33,3 @@ }

if (!utils_1.hasDirectivePrologue(parent))
return;
return; // not a directive
let reason = this.strictFile;

@@ -40,3 +40,3 @@ for (const statement of parent.statements) {

reason = this.isInStrictContext(parent.parent.parent) ||
(this.hasUseStrictDirective(this.sourceFile) ? "a parent node is already in strict mode" : undefined);
(this.hasUseStrictDirective(this.sourceFile) ? "a parent node is already in strict mode" /* Parent */ : undefined);
if (reason !== undefined)

@@ -47,5 +47,5 @@ this.addFindingAtNode(directive, `Redundant 'use strict': ${reason}.`, ymir_1.Replacement.delete(directive.pos, directive.end));

if (!tsutils_1.isExpressionStatement(statement) || !tsutils_1.isStringLiteral(statement.expression))
return;
return; // not a directive
if (reason === undefined && statement.expression.getText(this.sourceFile).slice(1, -1) === 'use strict')
reason = "there is already a 'use strict' directive in this prologue";
reason = "there is already a 'use strict' directive in this prologue" /* Antedecent */;
}

@@ -58,9 +58,9 @@ }

case ts.SyntaxKind.ClassExpression:
return "classes are always in strict mode";
return "classes are always in strict mode" /* Class */;
case ts.SyntaxKind.SourceFile:
return;
return; // SourceFile was already checked
}
if (utils_1.hasDirectivePrologue(node)) {
if (this.hasUseStrictDirective(node))
return "a parent node is already in strict mode";
return "a parent node is already in strict mode" /* Parent */;
node = node.parent.parent;

@@ -67,0 +67,0 @@ }

@@ -18,2 +18,3 @@ "use strict";

else if (catchClause !== undefined && isRethrow(catchClause)) {
// reminder for myself: empty catch clause can be used to simply ignore errors and is never redundant
this.addFinding(start, node.end, `${hasFinally ? "'catch' clause" : "'try' statement"} is unnecessary because the 'catch' clause only rethrows the error.`, hasFinally

@@ -20,0 +21,0 @@ ? ymir_1.Replacement.delete(catchClause.getStart(this.sourceFile), finallyBlock.pos - 'finally'.length)

@@ -91,4 +91,7 @@ "use strict";

return [
/* Add assignment after directives and super() call (if they exists) */
ymir_1.Replacement.append(getEndOfDirectivesAndSuper(construct), `${lineBreak}this.${param.name.getText()} = ${param.name.getText()};`),
/* Add property to class */
ymir_1.Replacement.append(construct.getStart(), getPropertyTextFromParam(param) + lineBreak),
/* Finally, delete modifiers from the parameter prop */
ymir_1.Replacement.delete(param.modifiers.pos, param.modifiers.end),

@@ -98,2 +101,3 @@ ];

function getFixerForLonghandProp(param, construct) {
/* Class member and assignment to be removed */
const member = construct

@@ -108,2 +112,3 @@ .parent.members.filter(tsutils_1.isPropertyDeclaration)

ymir_1.Replacement.delete(assignment.pos, assignment.end),
/* Prepend access modifiers to parameter declaration */
ymir_1.Replacement.append(param.name.getStart(), member.modifiers ? member.modifiers.map((mod) => mod.getText()).join(' ') + ' ' : 'public '),

@@ -118,2 +123,6 @@ ];

}
/**
* If directive(s) such as 'use strict' exist in the constructor body, returns
* the last directive, otherwise undefined.
*/
function getFinalDirectiveIndex(construct) {

@@ -130,2 +139,5 @@ let finalDirectiveIndex = -1;

}
/**
* Takes a parameter property and returns the text of the equivalent class element.
*/
function getPropertyTextFromParam(param) {

@@ -141,2 +153,5 @@ const modifiers = param.modifiers ? param.modifiers.map((mod) => mod.getText()).join(' ') : '';

}
/**
* Returns the super call expression index if it exists, otherwise -1.
*/
function getSuperCallIndex(construct) {

@@ -149,2 +164,5 @@ const statementIndex = getFinalDirectiveIndex(construct) + 1;

}
/**
* Returns true when the prop is assigned any value other than the param identifier.
*/
function isNonParamAssignmentToProp(stmt, param) {

@@ -165,2 +183,5 @@ return (tsutils_1.isBinaryExpression(stmt.expression) &&

}
/**
* Checks if a statement assigns the parameter to a class member property.
*/
function isSimpleParamToPropAssignment(stmt, param) {

@@ -180,2 +201,6 @@ return (tsutils_1.isExpressionStatement(stmt) &&

}
/**
* We can only autofix longhand props -> param props if certain conditions are met within the constructor body.
* See https://github.com/fimbullinter/wotan/issues/167#issuecomment-378945862 for more details on these conditions.
*/
function isStatementWithPossibleSideEffects(stmt, param) {

@@ -189,2 +214,6 @@ return (!tsutils_1.isExpressionStatement(stmt) ||

}
/**
* Returns all statements in a constructor body with the exception of super calls
* and directives such as 'use strict'.
*/
function statementsMinusDirectivesAndSuper(construct) {

@@ -191,0 +220,0 @@ return construct.body.statements.slice(Math.max(getFinalDirectiveIndex(construct), getSuperCallIndex(construct)) + 1);

@@ -124,6 +124,6 @@ "use strict";

if (getFunctionScopeBoundary(useScope) !== functionScope)
return true;
return true; // maybe used before declaration
for (const deadZone of deadZones)
if (use.location.pos >= deadZone.pos && use.location.pos < deadZone.end)
return true;
return true; // used in temporal dead zone
}

@@ -130,0 +130,0 @@ }

@@ -52,3 +52,3 @@ "use strict";

if (type.flags & ts.TypeFlags.StringLike)
return this.sourceFile.languageVersion >= ts.ScriptTarget.ES5;
return this.sourceFile.languageVersion >= ts.ScriptTarget.ES5; // iterating string is only possible starting from ES5
if (type.symbol !== undefined && /^(Concat|Readonly)?Array$/.test(type.symbol.escapedName) &&

@@ -63,2 +63,3 @@ type.symbol.declarations !== undefined && type.symbol.declarations.some(this.isDeclaredInDefaultLib, this))

isDeclaredInDefaultLib(node) {
// we assume it's the global array type if it comes from any lib.xxx.d.ts file
return path.normalize(path.dirname(node.getSourceFile().fileName))

@@ -132,5 +133,5 @@ === path.dirname(ts.getDefaultLibFilePath(this.context.compilerOptions));

if (use.pos < statement.pos || use.end > statement.end)
return false;
return false; // used outside of the loop
if (use.pos < statement.statement.pos)
continue;
continue; // uses in loop header are already checked
const parent = use.parent;

@@ -144,3 +145,3 @@ if (!tsutils_1.isElementAccessExpression(parent) ||

}
return arrayAccess;
return arrayAccess; // needs at least one array access
}

@@ -154,5 +155,7 @@ function extractArrayVariable(condition, indexVariable) {

case ts.SyntaxKind.LessThanToken:
// i < foo.length
({ left, right } = condition);
break;
case ts.SyntaxKind.GreaterThanToken:
// foo.length > i
({ left: right, right: left } = condition);

@@ -168,2 +171,3 @@ break;

function extractIndexVariable(initializer, incrementor) {
// there must be only one variable declared
if (!tsutils_1.isVariableDeclarationList(initializer) ||

@@ -173,5 +177,7 @@ initializer.declarations.length !== 1)

const declaration = initializer.declarations[0];
// variable must be initialized to 0
if (declaration.name.kind !== ts.SyntaxKind.Identifier || declaration.initializer === undefined ||
!isNumber(declaration.initializer, 0))
return;
// variable must be incremented by one
if (!isIncrementedByOne(incrementor, declaration.name.text))

@@ -183,2 +189,3 @@ return;

if (tsutils_1.isPostfixUnaryExpression(node) || tsutils_1.isPrefixUnaryExpression(node))
// ++var or var++
return node.operator === ts.SyntaxKind.PlusPlusToken && isVariable(node.operand, name);

@@ -189,4 +196,6 @@ if (!tsutils_1.isBinaryExpression(node))

case ts.SyntaxKind.PlusEqualsToken:
// var += 1
return isVariable(node.left, name) && isNumber(node.right, 1);
case ts.SyntaxKind.EqualsToken:
// var = var + 1 or var = 1 + var
return isVariable(node.left, name) &&

@@ -193,0 +202,0 @@ tsutils_1.isBinaryExpression(node.right) && node.right.operatorToken.kind === ts.SyntaxKind.PlusToken &&

@@ -82,2 +82,3 @@ "use strict";

}
// remove empty object iteral and the following comma if exists
fix.push(ymir_1.Replacement.delete(removedPrevious ? arg.getStart(sourceFile) : arg.pos, end));

@@ -88,3 +89,7 @@ removedPrevious = true;

const start = arg.getStart(sourceFile);
fix.push(ymir_1.Replacement.delete(start, start + 1), ymir_1.Replacement.delete(arg.properties[arg.properties.length - 1].end, arg.end));
fix.push(
// remove open brace
ymir_1.Replacement.delete(start, start + 1),
// remove trailing comma if exists and close brace
ymir_1.Replacement.delete(arg.properties[arg.properties.length - 1].end, arg.end));
removedPrevious = false;

@@ -91,0 +96,0 @@ }

@@ -18,2 +18,3 @@ "use strict";

node.tryBlock.statements.forEach(this.visitStatement, this);
// special handling for catchClause only if finallyBlock is present
if (node.catchClause !== undefined && node.finallyBlock !== undefined)

@@ -20,0 +21,0 @@ node.catchClause.block.statements.forEach(this.visitStatement, this);

@@ -59,2 +59,3 @@ "use strict";

let parent = node.parent;
// fixing unary expression like 'await <T>foo' to 'await foo as T' asserts the result of the entire expression, not just the operand
switch (parent.kind) {

@@ -67,2 +68,3 @@ case ts.SyntaxKind.PrefixUnaryExpression:

}
// fixing '<T>foo & bar' to 'foo as T & bar' would parse 'T & bar' as intersection type, therefore we need to add parens
while (tsutils_1.isBinaryExpression(parent)) {

@@ -69,0 +71,0 @@ if (node === parent.left) {

@@ -33,5 +33,7 @@ "use strict";

return false;
// falls through
case ts.SyntaxKind.ArrowFunction:
if (node.body.kind !== ts.SyntaxKind.Block)
return false;
// falls through
case ts.SyntaxKind.FunctionExpression:

@@ -102,2 +104,8 @@ break;

function expressionNeedsParensWhenReplacingNode(expr, replaced) {
// this currently doesn't handle the following cases
// (yield) as any
// await (yield)
// (1).toString()
// ({foo} = {foo: 1});
// binary operator precendence
while (true) {

@@ -104,0 +112,0 @@ switch (expr.kind) {

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc