1- import { CallExpression } from "typescript" ;
1+ import {
2+ AST_NODE_TYPES ,
3+ CallExpression ,
4+ CallExpressionArgument ,
5+ Literal
6+ } from "@typescript-eslint/types/dist/ast-spec" ;
27import { createRule } from "../create-rule" ;
38
9+ /**
10+ * Typescript type validation/conversion method
11+ * @param arg variable to type check
12+ * @returns arg as type Literal
13+ */
14+ function isLiteral ( arg : CallExpressionArgument ) : arg is Literal {
15+ // from ast-spec types Expression -> LiteralExpression -> Literal, all literals have attribute 'value'
16+ return arg . type === AST_NODE_TYPES . Literal ;
17+ }
18+
19+ /**
20+ * Type safe method to extract possible test name of type string
21+ * Prevents user from providing invalid arg0 to `test()` which can cause eslint to crash
22+ * @param node Eslint evaluated ASTTree Node based on CallExpression[callee.name=test]
23+ * @returns string of possible test name
24+ */
25+ function getValidTestName ( node : CallExpression ) : string {
26+ if ( ! isLiteral ( node . arguments [ 0 ] ) ) {
27+ throw new Error ( "Not a Literal expression." ) ;
28+ }
29+ const arg0value : Literal [ "value" ] = node . arguments [ 0 ] . value ;
30+ if (
31+ arg0value === null ||
32+ Object . getPrototypeOf ( arg0value ) === RegExp . prototype
33+ ) {
34+ throw new Error (
35+ "Unusable Literal value for test names (NullLiteral & RegExpLiteral)"
36+ ) ;
37+ }
38+ return arg0value . toString ( ) ;
39+ }
40+
441//------------------------------------------------------------------------------
542// Rule Definition
643//------------------------------------------------------------------------------
7-
844export default createRule ( {
945 name : __filename ,
1046 defaultOptions : [ ] ,
1147 meta : {
12- type :"problem" ,
48+ type : "problem" ,
1349 messages : {
1450 noIdenticalTitles : "Don't use identical titles for your tests"
1551 } ,
@@ -20,31 +56,37 @@ export default createRule({
2056 } ,
2157 schema : [ ]
2258 } ,
23-
59+
2460 create ( context ) {
25- const testTitles : { [ key : string ] :[ any ] } = { } ;
61+ const testTitles : { [ key : string ] : [ CallExpression ] } = { } ;
2662 return {
27- [ "CallExpression[callee.name=test]" ] ( node :any ) {
28- const testTitle = node . arguments [ 0 ] . value as string ;
29- if ( testTitle in testTitles ) {
30- testTitles [ testTitle ] . push ( node )
63+ "CallExpression[callee.name=test]" : ( node : CallExpression ) => {
64+ let testTitle ;
65+ try {
66+ testTitle = getValidTestName ( node ) ;
67+ } catch ( e ) {
68+ return ;
69+ }
70+ if ( testTitle in testTitles ) {
71+ testTitles [ testTitle ] . push ( node ) ;
3172 } else {
3273 testTitles [ testTitle ] = [ node ] ;
3374 }
3475 // const title = testTitles[testTitle];
3576 // testTitles[testTitle] = [title, node]
36-
3777 } ,
38- [ "Program:exit" ] ( ) {
39- Object . keys ( testTitles ) . forEach ( testTitle => {
40- if ( testTitles [ testTitle ] . length > 1 ) {
41- for ( const testTitleNode of testTitles [ testTitle ] ) {
42- context . report ( { node : testTitleNode , messageId : "noIdenticalTitles" } )
43-
44- }
78+ "Program:exit" : ( ) => {
79+ Object . values ( testTitles ) . forEach ( ( nodeList ) => {
80+ if ( nodeList . length > 1 ) {
81+ nodeList . forEach ( ( testTitleNode ) => {
82+ context . report ( {
83+ node : testTitleNode ,
84+ messageId : "noIdenticalTitles"
85+ } ) ;
86+ } ) ;
4587 }
46- } )
88+ } ) ;
4789 }
48- }
90+ } ;
4991 }
5092} ) ;
0 commit comments