diff --git a/src/rules/requireReadonlyReactProps.js b/src/rules/requireReadonlyReactProps.js index ad7bf4e..8cb0bee 100644 --- a/src/rules/requireReadonlyReactProps.js +++ b/src/rules/requireReadonlyReactProps.js @@ -14,6 +14,7 @@ const schema = [ const reComponentName = /^(Pure)?Component$/u; const reReadOnly = /^\$(ReadOnly|FlowFixMe)$/u; +const reReadOnlyTypeScript = /^(Readonly|$FlowFixMe)$/u; const isReactComponent = (node) => { if (!node.superClass) { @@ -69,15 +70,20 @@ const isReadOnlyObjectUnionType = (node, options) => { }); }; +const isReadOnlyName = (name, {useExperimentalTypeScriptSyntax}) => { + return useExperimentalTypeScriptSyntax ? reReadOnlyTypeScript.test(name) : reReadOnly.test(name); +}; + const isReadOnlyType = (node, options) => { - return node.right.id && reReadOnly.test(node.right.id.name) || + return node.right.id && isReadOnlyName(node.right.id.name, options) || isReadOnlyObjectType(node.right, options) || isReadOnlyObjectUnionType(node.right, options); }; const create = (context) => { const useImplicitExactTypes = _.get(context, ['options', 0, 'useImplicitExactTypes'], false); - const options = {useImplicitExactTypes}; + const useExperimentalTypeScriptSyntax = _.get(context, ['options', 0, 'useExperimentalTypeScriptSyntax'], false); + const options = {useImplicitExactTypes, useExperimentalTypeScriptSyntax}; const readOnlyTypes = []; const foundTypes = []; @@ -86,7 +92,7 @@ const create = (context) => { const isReadOnlyClassProp = (node) => { const id = node.superTypeParameters && node.superTypeParameters.params[0].id; - return id && !reReadOnly.test(id.name) && !readOnlyTypes.includes(id.name) && foundTypes.includes(id.name); + return id && !isReadOnlyName(id.name, options) && !readOnlyTypes.includes(id.name) && foundTypes.includes(id.name); }; for (const node of context.getSourceCode().ast.body) { @@ -153,7 +159,7 @@ const create = (context) => { if ((identifier = typeAnnotation.typeAnnotation.id) && foundTypes.includes(identifier.name) && !readOnlyTypes.includes(identifier.name) && - !reReadOnly.test(identifier.name)) { + !isReadOnlyName(identifier.name, options)) { if (reportedFunctionalComponents.includes(identifier)) { return; } diff --git a/tests/rules/assertions/requireReadonlyReactProps.js b/tests/rules/assertions/requireReadonlyReactProps.js index 67af5f7..f8a94bb 100644 --- a/tests/rules/assertions/requireReadonlyReactProps.js +++ b/tests/rules/assertions/requireReadonlyReactProps.js @@ -10,6 +10,19 @@ export default { }, ], }, + { + code: 'type Props = { }; class Foo extends React.Component { }', + errors: [ + { + message: 'Props must be Readonly', + }, + ], + options: [ + { + useExperimentalTypeScriptSyntax: true, + }, + ], + }, { code: 'type OtherProps = { foo: string }; class Foo extends React.Component { }', errors: [ @@ -118,6 +131,24 @@ export default { }, ], }, + { + // vvvvv + code: 'type Props = { }; function Foo(props: Props) { return

}', + errors: [ + { + column: 39, + endColumn: 44, + endLine: 1, + line: 1, + message: 'Props must be Readonly', + }, + ], + options: [ + { + useExperimentalTypeScriptSyntax: true, + }, + ], + }, { code: 'type Props = { }; function Foo(props: Props) { return foo ?

: }', errors: [ @@ -149,6 +180,14 @@ export default { { code: 'class Foo extends React.Component<$ReadOnly<{}>> { }', }, + { + code: 'class Foo extends React.Component> { }', + options: [ + { + useExperimentalTypeScriptSyntax: true, + }, + ], + }, { code: 'type Props = $ReadOnly<{}>; class Foo extends React.Component { }', }, @@ -251,6 +290,14 @@ export default { { code: 'type Props = $ReadOnly<{}>; function Foo(props: Props) { }', }, + { + code: 'type Props = Readonly<{}>; function Foo(props: Props) { }', + options: [ + { + useExperimentalTypeScriptSyntax: true, + }, + ], + }, { code: 'type Props = {}; function Foo(props: OtherProps) { }', },