diff --git a/src/reactUnusedPropsAndStateRule.ts b/src/reactUnusedPropsAndStateRule.ts index 524a7fd..57410c1 100644 --- a/src/reactUnusedPropsAndStateRule.ts +++ b/src/reactUnusedPropsAndStateRule.ts @@ -78,6 +78,8 @@ function walk(ctx: Lint.WalkContext) { let stateNames: string[] = []; let stateNodes: { [index: string]: ts.TypeElement } = {}; const classDeclarations: ts.ClassDeclaration[] = []; + const arrowFunctions: ts.ArrowFunction[] = []; + const functionComponents: ts.FunctionBody[] = []; let propsAlias: string | undefined; let stateAlias: string | undefined; @@ -96,6 +98,36 @@ function walk(ctx: Lint.WalkContext) { return result; } + function getTypeLiteralData(node: ts.TypeLiteralNode): { [index: string]: ts.TypeElement } { + const result: { [index: string]: ts.TypeElement } = {}; + node.members.forEach( + (typeElement: ts.TypeElement): void => { + if (typeElement.name !== undefined) { + const text = typeElement.name.getText(); + if (text !== undefined) { + result[text] = typeElement; + } + } + } + ); + return result; + } + + function getObjectBindingData(node: ts.ObjectBindingPattern): { [index: string]: ts.BindingElement } { + const result: { [index: string]: ts.BindingElement } = {}; + node.elements.forEach( + (element: ts.BindingElement): void => { + if (element.name !== undefined) { + const text = element.name.getText(); + if (text !== undefined) { + result[text] = element; + } + } + } + ); + return result; + } + function isParentNodeSuperCall(node: ts.Node): boolean { if (node.parent !== undefined && node.parent.kind === ts.SyntaxKind.CallExpression) { const call: ts.CallExpression = node.parent; @@ -104,6 +136,113 @@ function walk(ctx: Lint.WalkContext) { return false; } + function inspectPropUsageInObjectBinding(name: ts.ObjectBindingPattern): void { + const bindingElements = getObjectBindingData(name); + const foundPropNames = Object.keys(bindingElements); + + for (const propName of foundPropNames) { + propNames = Utils.remove(propNames, propName); + } + } + + function lookForReactSpecificArrowFunction(node: ts.TypeReferenceNode): void { + const nodeTypeText = node.typeName.getText(); + + const isReactFunctionComponentType = + nodeTypeText === 'React.SFC' || + nodeTypeText === 'SFC' || + nodeTypeText === 'React.FC' || + nodeTypeText === 'FC' || + nodeTypeText === 'React.StatelessComponent' || + nodeTypeText === 'StatelessComponent' || + nodeTypeText === 'React.FunctionComponent' || + nodeTypeText === 'FunctionComponent'; + + if (!isReactFunctionComponentType) { + return; + } + + if (!node.typeArguments || node.typeArguments.length !== 1) { + return; + } + + const typeArgument = node.typeArguments[0]; + + if (tsutils.isTypeLiteralNode(typeArgument)) { + propNodes = getTypeLiteralData(typeArgument); + propNames = Object.keys(propNodes); + } else { + // we have a TypeReference here which we expect to have been parsed + // previously in the AST + } + + // the arrow function should be a sibling of this type reference node + const arrowFunction = tsutils.getChildOfKind(node.parent, ts.SyntaxKind.ArrowFunction); + + if (!arrowFunction || !tsutils.isArrowFunction(arrowFunction)) { + return; + } + + lookForArrowFunction(arrowFunction); + } + + function lookForArrowFunction(node: ts.ArrowFunction): void { + // expect one parameter for the function + const parameters = node.parameters; + if (parameters.length !== 1) { + return; + } + + const firstParameter = parameters[0]; + const { name, type } = firstParameter; + if (type && tsutils.isTypeReferenceNode(type)) { + const typeName = type.typeName.getText(); + // skip any type that doesn't match the expected regex + if (!ctx.options.propsInterfaceRegex.test(typeName)) { + return; + } + } + + if (tsutils.isIdentifier(name)) { + propsAlias = name.getText(); + } else if (tsutils.isObjectBindingPattern(name)) { + inspectPropUsageInObjectBinding(name); + } + + arrowFunctions.push(node); + } + + function lookForFunctionComponent(node: ts.FunctionDeclaration | ts.FunctionExpression): void { + // if no body found, no need to traverse + if (!node.body) { + return; + } + + // expect one parameter for the function + const parameters = node.parameters; + if (parameters.length !== 1) { + return; + } + + const firstParameter = parameters[0]; + const { name, type } = firstParameter; + if (type && tsutils.isTypeReferenceNode(type)) { + const typeName = type.typeName.getText(); + // skip any type that doesn't match the expected regex + if (!ctx.options.propsInterfaceRegex.test(typeName)) { + return; + } + } + + if (tsutils.isIdentifier(name)) { + propsAlias = name.getText(); + } else if (tsutils.isObjectBindingPattern(name)) { + inspectPropUsageInObjectBinding(name); + } + + functionComponents.push(node.body); + } + function cb(node: ts.Node): void { // Accumulate class declarations here and only analyze // them *after* all interfaces have been analyzed. @@ -198,6 +337,14 @@ function walk(ctx: Lint.WalkContext) { stateNames = []; } } + } else if (tsutils.isTypeReferenceNode(node)) { + lookForReactSpecificArrowFunction(node); + } else if (tsutils.isArrowFunction(node)) { + lookForArrowFunction(node); + } else if (tsutils.isFunctionDeclaration(node)) { + lookForFunctionComponent(node); + } else if (tsutils.isFunctionExpression(node)) { + lookForFunctionComponent(node); } return ts.forEachChild(node, cb); @@ -205,9 +352,12 @@ function walk(ctx: Lint.WalkContext) { ts.forEachChild(ctx.sourceFile, cb); - // If there are Props or State interfaces, then scan the classes now. + // if there are Props or State interfaces, traverse the identified components + // to find any usage of the members in these interfaces if (propNames.length > 0 || stateNames.length > 0) { classDeclarations.forEach(c => ts.forEachChild(c, cb)); + arrowFunctions.forEach(c => ts.forEachChild(c.body, cb)); + functionComponents.forEach(f => ts.forEachChild(f, cb)); } propNames.forEach( diff --git a/tests/react-unused-props-and-state/functions/arrow-functions/passes-for-object-binding.tsx.lint b/tests/react-unused-props-and-state/functions/arrow-functions/passes-for-object-binding.tsx.lint new file mode 100644 index 0000000..6e4a37a --- /dev/null +++ b/tests/react-unused-props-and-state/functions/arrow-functions/passes-for-object-binding.tsx.lint @@ -0,0 +1,10 @@ +import React = require('react'); + +interface SampleProps { + text: string; + wrap?: boolean; +} + +export const SampleArrowFunction = ({ text, wrap }: SampleProps) => { + return wrap ? {text} : text; +}; diff --git a/tests/react-unused-props-and-state/functions/arrow-functions/reports-for-object-binding.tsx.lint b/tests/react-unused-props-and-state/functions/arrow-functions/reports-for-object-binding.tsx.lint new file mode 100644 index 0000000..286c149 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/arrow-functions/reports-for-object-binding.tsx.lint @@ -0,0 +1,11 @@ +import React = require('react'); + +interface Props { + text: string; + wrap?: boolean; + ~~~~~~~~~~~~~~~ [Unused React property defined in interface: wrap] +} + +export const SampleArrowFunction = ({ text }: Props) => { + return {text}; +}; diff --git a/tests/react-unused-props-and-state/functions/arrow-functions/tslint.json b/tests/react-unused-props-and-state/functions/arrow-functions/tslint.json new file mode 100644 index 0000000..906deb0 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/arrow-functions/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "react-unused-props-and-state": true + } +} diff --git a/tests/react-unused-props-and-state/functions/function-components/passes-for-prop-alias-in-type-reference.tsx.lint b/tests/react-unused-props-and-state/functions/function-components/passes-for-prop-alias-in-type-reference.tsx.lint new file mode 100644 index 0000000..eb58db0 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/function-components/passes-for-prop-alias-in-type-reference.tsx.lint @@ -0,0 +1,9 @@ +import React = require('react'); + +interface Props { + readonly text: string +} + +export const SpanWithText: React.FunctionComponent = (p: Props) => ( + {p.text} +) diff --git a/tests/react-unused-props-and-state/functions/function-components/passes-for-prop-in-type-reference.tsx.lint b/tests/react-unused-props-and-state/functions/function-components/passes-for-prop-in-type-reference.tsx.lint new file mode 100644 index 0000000..85fb67c --- /dev/null +++ b/tests/react-unused-props-and-state/functions/function-components/passes-for-prop-in-type-reference.tsx.lint @@ -0,0 +1,9 @@ +import React = require('react'); + +interface Props { + readonly text: string +} + +export const SpanWithText: React.FunctionComponent = (props: Props) => ( + {props.text} +) diff --git a/tests/react-unused-props-and-state/functions/function-components/reports-for-explicit-import-shorthand.tsx.lint b/tests/react-unused-props-and-state/functions/function-components/reports-for-explicit-import-shorthand.tsx.lint new file mode 100644 index 0000000..429f219 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/function-components/reports-for-explicit-import-shorthand.tsx.lint @@ -0,0 +1,11 @@ +import * as React, { FC } from 'react'; + +interface Props { + readonly text: string + readonly wrap?: boolean; + ~~~~~~~~~~~~~~~~~~~~~~~~ [Unused React property defined in interface: wrap] +} + +export const SpanWithText: FC = (props: Props) => ( + {props.text} +) diff --git a/tests/react-unused-props-and-state/functions/function-components/reports-for-explicit-import.tsx.lint b/tests/react-unused-props-and-state/functions/function-components/reports-for-explicit-import.tsx.lint new file mode 100644 index 0000000..bf9b4a1 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/function-components/reports-for-explicit-import.tsx.lint @@ -0,0 +1,11 @@ +import * as React, { FunctionComponent } from 'react'; + +interface Props { + readonly text: string + readonly wrap?: boolean; + ~~~~~~~~~~~~~~~~~~~~~~~~ [Unused React property defined in interface: wrap] +} + +export const SpanWithText: FunctionComponent = (props: Props) => ( + {props.text} +) diff --git a/tests/react-unused-props-and-state/functions/function-components/tslint.json b/tests/react-unused-props-and-state/functions/function-components/tslint.json new file mode 100644 index 0000000..906deb0 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/function-components/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "react-unused-props-and-state": true + } +} diff --git a/tests/react-unused-props-and-state/functions/functions/passes-for-example-from-issue-339.tsx.lint b/tests/react-unused-props-and-state/functions/functions/passes-for-example-from-issue-339.tsx.lint new file mode 100644 index 0000000..b60f7ce --- /dev/null +++ b/tests/react-unused-props-and-state/functions/functions/passes-for-example-from-issue-339.tsx.lint @@ -0,0 +1,18 @@ +import React = require('react'); + +interface Props { + children?: React.ReactNode; + heading: string; + className?: string; +} + +export function TestComponent(props: Props) { + return ( +
+

{props.heading}

+ {props.children} +
+ ) +} + +export default TestComponent; diff --git a/tests/react-unused-props-and-state/functions/functions/passes-for-function-when-prop-in-type-reference.tsx.lint b/tests/react-unused-props-and-state/functions/functions/passes-for-function-when-prop-in-type-reference.tsx.lint new file mode 100644 index 0000000..bdab625 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/functions/passes-for-function-when-prop-in-type-reference.tsx.lint @@ -0,0 +1,14 @@ +import React = require('react'); + +interface Props { + heading: string; + className?: string; +} + +export function TestComponent(props: Props) { + return ( +
+

{props.heading}

+
+ ) +} diff --git a/tests/react-unused-props-and-state/functions/functions/passes-for-object-binding.tsx.lint b/tests/react-unused-props-and-state/functions/functions/passes-for-object-binding.tsx.lint new file mode 100644 index 0000000..a2ad619 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/functions/passes-for-object-binding.tsx.lint @@ -0,0 +1,10 @@ +import React = require('react'); + +interface SampleProps { + text: string; + wrap?: boolean; +} + +export const SampleFunctionExpression = function ({ text, wrap }: SampleProps) { + return wrap ? {text} : text; +}; diff --git a/tests/react-unused-props-and-state/functions/functions/passes-for-prop-alias-in-type-reference.tsx.lint b/tests/react-unused-props-and-state/functions/functions/passes-for-prop-alias-in-type-reference.tsx.lint new file mode 100644 index 0000000..13b926a --- /dev/null +++ b/tests/react-unused-props-and-state/functions/functions/passes-for-prop-alias-in-type-reference.tsx.lint @@ -0,0 +1,14 @@ +import React = require('react'); + +interface Props { + heading: string; + className?: string; +} + +export function TestComponent(p: Props) { + return ( +
+

{p.heading}

+
+ ) +} diff --git a/tests/react-unused-props-and-state/functions/functions/reports-for-object-binding.tsx.lint b/tests/react-unused-props-and-state/functions/functions/reports-for-object-binding.tsx.lint new file mode 100644 index 0000000..26e80f1 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/functions/reports-for-object-binding.tsx.lint @@ -0,0 +1,11 @@ +import React = require('react'); + +interface Props { + text: string; + wrap?: boolean; + ~~~~~~~~~~~~~~~ [Unused React property defined in interface: wrap] +} + +export const SampleFunctionExpression = function ({ text }: Props) { + return {text}; +}; diff --git a/tests/react-unused-props-and-state/functions/functions/reports-for-prop-missing-in-type-reference.tsx.lint b/tests/react-unused-props-and-state/functions/functions/reports-for-prop-missing-in-type-reference.tsx.lint new file mode 100644 index 0000000..88af6e5 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/functions/reports-for-prop-missing-in-type-reference.tsx.lint @@ -0,0 +1,18 @@ +import React = require('react'); + +interface Props { + children?: React.ReactNode; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Unused React property defined in interface: children] + heading: string; + className?: string; +} + +export function TestComponent(props: Props) { + return ( +
+

{props.heading}

+
+ ) +} + +export default TestComponent; diff --git a/tests/react-unused-props-and-state/functions/functions/tslint.json b/tests/react-unused-props-and-state/functions/functions/tslint.json new file mode 100644 index 0000000..906deb0 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/functions/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "react-unused-props-and-state": true + } +} diff --git a/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-all-props-used-in-jsx-return-value.tsx.lint b/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-all-props-used-in-jsx-return-value.tsx.lint new file mode 100644 index 0000000..599a520 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-all-props-used-in-jsx-return-value.tsx.lint @@ -0,0 +1,14 @@ +import React = require('react'); + +interface Props { + children: JSX.Element | JSX.Element[]; + className: string; +} + +const ButtonGroup: React.SFC = (props: Props) => { + return ( +
+ {props.children} +
+ ); +}; diff --git a/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-jsx-assigned-to-variable.tsx.lint b/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-jsx-assigned-to-variable.tsx.lint new file mode 100644 index 0000000..89b511e --- /dev/null +++ b/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-jsx-assigned-to-variable.tsx.lint @@ -0,0 +1,16 @@ +import React = require('react'); + +interface Props { + children: JSX.Element | JSX.Element[]; + className: string; +} + +const ButtonGroup: React.SFC = (props: Props) => { + const container = ( +
+ {props.children} +
+ ); + + return container; +}; diff --git a/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-prop-alias-in-type-literal.tsx.lint b/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-prop-alias-in-type-literal.tsx.lint new file mode 100644 index 0000000..f6c0c02 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-prop-alias-in-type-literal.tsx.lint @@ -0,0 +1,7 @@ +import React = require('react'); + +export const SpanWithText: React.SFC<{ + readonly text: string +}> = (p) => ( + {p.text} +) diff --git a/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-prop-alias-in-type-reference.tsx.lint b/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-prop-alias-in-type-reference.tsx.lint new file mode 100644 index 0000000..5af5f72 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-prop-alias-in-type-reference.tsx.lint @@ -0,0 +1,9 @@ +import React = require('react'); + +interface Props { + readonly text: string +} + +export const SpanWithText: React.SFC = (p: Props) => ( + {p.text} +) diff --git a/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-prop-in-type-literal.tsx.lint b/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-prop-in-type-literal.tsx.lint new file mode 100644 index 0000000..7c26279 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-prop-in-type-literal.tsx.lint @@ -0,0 +1,7 @@ +import React = require('react'); + +export const SpanWithText: React.SFC<{ + readonly text: string +}> = (props) => ( + {props.text} +) diff --git a/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-prop-in-type-reference.tsx.lint b/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-prop-in-type-reference.tsx.lint new file mode 100644 index 0000000..ffdf0f1 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/stateless-functions/passes-for-prop-in-type-reference.tsx.lint @@ -0,0 +1,9 @@ +import React = require('react'); + +interface Props { + readonly text: string +} + +export const SpanWithText: React.SFC = (props: Props) => ( + {props.text} +) diff --git a/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-explicit-import-shorthand.tsx.lint b/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-explicit-import-shorthand.tsx.lint new file mode 100644 index 0000000..532c388 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-explicit-import-shorthand.tsx.lint @@ -0,0 +1,11 @@ +import * as React, { SFC } from 'react'; + +interface Props { + readonly text: string + readonly wrap?: boolean; + ~~~~~~~~~~~~~~~~~~~~~~~~ [Unused React property defined in interface: wrap] +} + +export const SpanWithText: SFC = (props: Props) => ( + {props.text} +) diff --git a/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-explicit-import.tsx.lint b/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-explicit-import.tsx.lint new file mode 100644 index 0000000..281b7fa --- /dev/null +++ b/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-explicit-import.tsx.lint @@ -0,0 +1,11 @@ +import * as React, { StatelessComponent } from 'react'; + +interface Props { + readonly text: string + readonly wrap?: boolean; + ~~~~~~~~~~~~~~~~~~~~~~~~ [Unused React property defined in interface: wrap] +} + +export const SpanWithText: StatelessComponent = (props: Props) => ( + {props.text} +) diff --git a/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-alias-in-type-literal.tsx.lint b/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-alias-in-type-literal.tsx.lint new file mode 100644 index 0000000..40a34a8 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-alias-in-type-literal.tsx.lint @@ -0,0 +1,9 @@ +import React = require('react'); + +export const SpanWithText: React.SFC<{ + readonly text: string, + readonly somethingElse: string, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Unused React property defined in interface: somethingElse] +}> = (p) => ( + {p.text} +) diff --git a/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-alias-in-type-reference.tsx.lint b/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-alias-in-type-reference.tsx.lint new file mode 100644 index 0000000..dc9f88a --- /dev/null +++ b/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-alias-in-type-reference.tsx.lint @@ -0,0 +1,11 @@ +import React = require('react'); + +interface Props { + readonly text: string + readonly somethingElse: string + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Unused React property defined in interface: somethingElse] +} + +export const SpanWithText: React.SFC = (p: Props) => ( + {p.text} +) diff --git a/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-in-complex-statement.tsx.lint b/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-in-complex-statement.tsx.lint new file mode 100644 index 0000000..97859b0 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-in-complex-statement.tsx.lint @@ -0,0 +1,37 @@ +import React = require('react'); + +interface Props { + readonly text: string + readonly highlight: ReadonlyArray + readonly somethingElse: string + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Unused React property defined in interface: somethingElse] +} + +export const HighlightText: React.SFC = (props) => ( + + { + props.text + .split('') + .map((ch, i): [string, boolean] => [ch, props.highlight.includes(i)]) + .concat([['', false]]) + .reduce( + (state, [ch, matched], i, arr) => { + if (matched === state.matched && i < arr.length - 1) { + state.str += ch + } else { + const Component = state.matched ? 'mark' : 'span' + state.result.push({state.str}) + state.str = ch + state.matched = matched + } + return state + }, + { + matched: false, + str: '', + result: new Array>(), + } + ).result + } + +) diff --git a/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-in-object-binding-and-complex-statement.tsx.lint b/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-in-object-binding-and-complex-statement.tsx.lint new file mode 100644 index 0000000..4cf9aa1 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-in-object-binding-and-complex-statement.tsx.lint @@ -0,0 +1,40 @@ +import React = require('react'); + +interface Props { + readonly text: string + readonly highlight: ReadonlyArray + readonly somethingElse: string + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Unused React property defined in interface: somethingElse] +} + +export const HighlightText: React.SFC = ({ + text, + highlight, +}) => ( + + { + text + .split('') + .map((ch, i): [string, boolean] => [ch, highlight.includes(i)]) + .concat([['', false]]) + .reduce( + (state, [ch, matched], i, arr) => { + if (matched === state.matched && i < arr.length - 1) { + state.str += ch + } else { + const Component = state.matched ? 'mark' : 'span' + state.result.push({state.str}) + state.str = ch + state.matched = matched + } + return state + }, + { + matched: false, + str: '', + result: new Array>(), + } + ).result + } + +) diff --git a/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-in-type-literal.tsx.lint b/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-in-type-literal.tsx.lint new file mode 100644 index 0000000..2b16d4e --- /dev/null +++ b/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-in-type-literal.tsx.lint @@ -0,0 +1,9 @@ +import React = require('react'); + +export const SpanWithText: React.SFC<{ + readonly text: string, + readonly somethingElse: string, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Unused React property defined in interface: somethingElse] +}> = (prop) => ( + {prop.text} +) diff --git a/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-in-type-reference.tsx.lint b/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-in-type-reference.tsx.lint new file mode 100644 index 0000000..497b131 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/stateless-functions/reports-for-prop-in-type-reference.tsx.lint @@ -0,0 +1,11 @@ +import React = require('react'); + +interface Props { + readonly text: string + readonly somethingElse: string + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Unused React property defined in interface: somethingElse] +} + +export const SpanWithText: React.SFC = (props: Props) => ( + {props.text} +) diff --git a/tests/react-unused-props-and-state/functions/stateless-functions/tslint.json b/tests/react-unused-props-and-state/functions/stateless-functions/tslint.json new file mode 100644 index 0000000..906deb0 --- /dev/null +++ b/tests/react-unused-props-and-state/functions/stateless-functions/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "react-unused-props-and-state": true + } +}