@typescript-eslint/eslint-plugin
Advanced tools
Comparing version 1.3.1-alpha.0 to 1.3.1-alpha.1
@@ -18,116 +18,2 @@ /** | ||
/** | ||
* 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 | ||
* @param {ts.ObjectType} type type | ||
* @returns {boolean} true if type could be a tuple type | ||
*/ | ||
function couldBeTupleType(type) { | ||
const properties = type.getProperties(); | ||
if (properties.length === 0) { | ||
return false; | ||
} | ||
let i = 0; | ||
for (; i < properties.length; ++i) { | ||
const name = properties[i].name; | ||
if (String(i) !== name) { | ||
if (i === 0) { | ||
// if there are no integer properties, this is not a tuple | ||
return false; | ||
} | ||
break; | ||
} | ||
} | ||
for (; i < properties.length; ++i) { | ||
if (String(+properties[i].name) === properties[i].name) { | ||
return false; // if there are any other numeric properties, this is not a tuple | ||
} | ||
} | ||
return true; | ||
} | ||
/** | ||
* | ||
* @param {Node} node node being linted | ||
* @param {Context} context linting context | ||
* @param {ts.TypeChecker} checker TypeScript typechecker | ||
* @returns {void} | ||
*/ | ||
function checkNonNullAssertion(node, context, checker) { | ||
/** | ||
* Corresponding TSNode is guaranteed to be in map | ||
* @type {ts.NonNullExpression} | ||
*/ | ||
const originalNode = context.parserServices.esTreeNodeToTSNodeMap.get(node); | ||
const type = checker.getTypeAtLocation(originalNode.expression); | ||
if (type === checker.getNonNullableType(type)) { | ||
context.report({ | ||
node, | ||
messageId: 'unnecessaryAssertion', | ||
fix(fixer) { | ||
return fixer.removeRange([ | ||
originalNode.expression.end, | ||
originalNode.end | ||
]); | ||
} | ||
}); | ||
} | ||
} | ||
/** | ||
* @param {Node} node node being linted | ||
* @param {Context} context linting context | ||
* @param {ts.TypeChecker} checker TypeScript typechecker | ||
* @returns {void} | ||
*/ | ||
function verifyCast(node, context, checker) { | ||
/** | ||
* * Corresponding TSNode is guaranteed to be in map | ||
* @type {ts.AssertionExpression} | ||
*/ | ||
const originalNode = context.parserServices.esTreeNodeToTSNodeMap.get(node); | ||
const options = context.options[0]; | ||
if ( | ||
options && | ||
options.typesToIgnore && | ||
options.typesToIgnore.indexOf(originalNode.type.getText()) !== -1 | ||
) { | ||
return; | ||
} | ||
const castType = checker.getTypeAtLocation(originalNode); | ||
if ( | ||
tsutils.isTypeFlagSet(castType, ts.TypeFlags.Literal) || | ||
(tsutils.isObjectType(castType) && | ||
(tsutils.isObjectFlagSet(castType, ts.ObjectFlags.Tuple) || | ||
couldBeTupleType(castType))) | ||
) { | ||
// It's not always safe to remove a cast to a literal type or tuple | ||
// type, as those types are sometimes widened without the cast. | ||
return; | ||
} | ||
const uncastType = checker.getTypeAtLocation(originalNode.expression); | ||
if (uncastType === castType) { | ||
context.report({ | ||
node, | ||
messageId: 'unnecessaryAssertion', | ||
fix(fixer) { | ||
return originalNode.kind === ts.SyntaxKind.TypeAssertionExpression | ||
? fixer.removeRange([ | ||
originalNode.getStart(), | ||
originalNode.expression.getStart() | ||
]) | ||
: fixer.removeRange([originalNode.expression.end, originalNode.end]); | ||
} | ||
}); | ||
} | ||
} | ||
/** @type {import("eslint").Rule.RuleModule} */ | ||
@@ -166,13 +52,133 @@ module.exports = { | ||
create(context) { | ||
const sourceCode = context.getSourceCode(); | ||
const checker = util.getParserServices(context).program.getTypeChecker(); | ||
/** | ||
* 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 | ||
* @param {ts.ObjectType} type type | ||
* @returns {boolean} true if type could be a tuple type | ||
*/ | ||
function couldBeTupleType(type) { | ||
const properties = type.getProperties(); | ||
if (properties.length === 0) { | ||
return false; | ||
} | ||
let i = 0; | ||
for (; i < properties.length; ++i) { | ||
const name = properties[i].name; | ||
if (String(i) !== name) { | ||
if (i === 0) { | ||
// if there are no integer properties, this is not a tuple | ||
return false; | ||
} | ||
break; | ||
} | ||
} | ||
for (; i < properties.length; ++i) { | ||
if (String(+properties[i].name) === properties[i].name) { | ||
return false; // if there are any other numeric properties, this is not a tuple | ||
} | ||
} | ||
return true; | ||
} | ||
/** | ||
* @param {Node} node node being linted | ||
* @returns {void} | ||
*/ | ||
function checkNonNullAssertion(node) { | ||
/** | ||
* Corresponding TSNode is guaranteed to be in map | ||
* @type {ts.NonNullExpression} | ||
*/ | ||
const originalNode = context.parserServices.esTreeNodeToTSNodeMap.get( | ||
node | ||
); | ||
const type = checker.getTypeAtLocation(originalNode.expression); | ||
if (type === checker.getNonNullableType(type)) { | ||
context.report({ | ||
node, | ||
messageId: 'unnecessaryAssertion', | ||
fix(fixer) { | ||
return fixer.removeRange([ | ||
originalNode.expression.end, | ||
originalNode.end | ||
]); | ||
} | ||
}); | ||
} | ||
} | ||
/** | ||
* @param {Node} node node being linted | ||
* @returns {void} | ||
*/ | ||
function verifyCast(node) { | ||
const options = context.options[0]; | ||
if ( | ||
options && | ||
options.typesToIgnore && | ||
options.typesToIgnore.indexOf( | ||
sourceCode.getText(node.typeAnnotation) | ||
) !== -1 | ||
) { | ||
return; | ||
} | ||
/** | ||
* Corresponding TSNode is guaranteed to be in map | ||
* @type {ts.AssertionExpression} | ||
*/ | ||
const originalNode = context.parserServices.esTreeNodeToTSNodeMap.get( | ||
node | ||
); | ||
const castType = checker.getTypeAtLocation(originalNode); | ||
if ( | ||
tsutils.isTypeFlagSet(castType, ts.TypeFlags.Literal) || | ||
(tsutils.isObjectType(castType) && | ||
(tsutils.isObjectFlagSet(castType, ts.ObjectFlags.Tuple) || | ||
couldBeTupleType(castType))) | ||
) { | ||
// It's not always safe to remove a cast to a literal type or tuple | ||
// type, as those types are sometimes widened without the cast. | ||
return; | ||
} | ||
const uncastType = checker.getTypeAtLocation(originalNode.expression); | ||
if (uncastType === castType) { | ||
context.report({ | ||
node, | ||
messageId: 'unnecessaryAssertion', | ||
fix(fixer) { | ||
return originalNode.kind === ts.SyntaxKind.TypeAssertionExpression | ||
? fixer.removeRange([ | ||
originalNode.getStart(), | ||
originalNode.expression.getStart() | ||
]) | ||
: fixer.removeRange([ | ||
originalNode.expression.end, | ||
originalNode.end | ||
]); | ||
} | ||
}); | ||
} | ||
} | ||
return { | ||
TSNonNullExpression(node) { | ||
checkNonNullAssertion(node, context, checker); | ||
checkNonNullAssertion(node); | ||
}, | ||
TSTypeAssertion(node) { | ||
verifyCast(node, context, checker); | ||
verifyCast(node); | ||
}, | ||
TSAsExpression(node) { | ||
verifyCast(node, context, checker); | ||
verifyCast(node); | ||
} | ||
@@ -179,0 +185,0 @@ }; |
{ | ||
"name": "@typescript-eslint/eslint-plugin", | ||
"version": "1.3.1-alpha.0+d178499", | ||
"version": "1.3.1-alpha.1+b95c4cf", | ||
"description": "TypeScript plugin for ESLint", | ||
@@ -27,3 +27,3 @@ "keywords": [ | ||
"dependencies": { | ||
"@typescript-eslint/parser": "1.3.1-alpha.0+d178499", | ||
"@typescript-eslint/parser": "1.3.1-alpha.1+b95c4cf", | ||
"requireindex": "^1.2.0", | ||
@@ -40,3 +40,3 @@ "tsutils": "^3.7.0" | ||
}, | ||
"gitHead": "d1784992902a41ffc76e917c6b2227e487c76671" | ||
"gitHead": "b95c4cf803f42352976389494f604d0b94bbf644" | ||
} |
@@ -147,2 +147,3 @@ <h1 align="center">ESLint Plugin TypeScript</h1> | ||
| [`@typescript-eslint/no-var-requires`](./docs/rules/no-var-requires.md) | Disallows the use of require statements except in import statements (`no-var-requires` from TSLint) | :heavy_check_mark: | | | ||
| [`@typescript-eslint/prefer-function-type`](./docs/rules/prefer-function-type.md) | Use function types instead of interfaces with call signatures (`callable-types` from TSLint) | | :wrench: | | ||
| [`@typescript-eslint/prefer-interface`](./docs/rules/prefer-interface.md) | Prefer an interface declaration over a type literal (type T = { ... }) (`interface-over-type-literal` from TSLint) | :heavy_check_mark: | :wrench: | | ||
@@ -149,0 +150,0 @@ | [`@typescript-eslint/prefer-namespace-keyword`](./docs/rules/prefer-namespace-keyword.md) | Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules. (`no-internal-module` from TSLint) | :heavy_check_mark: | :wrench: | |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
286500
88
4768
155