blob: 141edc7b3a2b24c1ad67b7c0d81f49a5bd2c94a9 [file] [log] [blame]
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var schema = [];
var reComponentName = /^(Pure)?Component$/;
var reReadOnly = /^\$(ReadOnly|FlowFixMe)$/;
var isReactComponent = function isReactComponent(node) {
if (!node.superClass) {
return false;
}
return (
// class Foo extends Component { }
// class Foo extends PureComponent { }
node.superClass.type === 'Identifier' && reComponentName.test(node.superClass.name) ||
// class Foo extends React.Component { }
// class Foo extends React.PureComponent { }
node.superClass.type === 'MemberExpression' && node.superClass.object.name === 'React' && reComponentName.test(node.superClass.property.name)
);
};
var create = function create(context) {
var readOnlyTypes = [];
var foundTypes = [];
var reportedFunctionalComponents = [];
var isReadOnlyClassProp = function isReadOnlyClassProp(node) {
var id = node.superTypeParameters && node.superTypeParameters.params[0].id;
return id && !reReadOnly.test(id.name) && !readOnlyTypes.includes(id.name) && foundTypes.includes(id.name);
};
var isReadOnlyObjectType = function isReadOnlyObjectType(node) {
if (!node || node.type !== 'ObjectTypeAnnotation') {
return false;
}
// we consider `{||}` to be ReadOnly since it's exact AND has no props
if (node.exact && node.properties.length === 0) {
return true;
}
// { +foo: ..., +bar: ..., ... }
return node.properties.length > 0 && node.properties.every(function (prop) {
return prop.variance && prop.variance.kind === 'plus';
});
};
var isReadOnlyType = function isReadOnlyType(node) {
return node.right.id && reReadOnly.test(node.right.id.name) || isReadOnlyObjectType(node.right);
};
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = context.getSourceCode().ast.body[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var node = _step.value;
var idName = void 0;
var typeNode = void 0;
// type Props = $ReadOnly<{}>
if (node.type === 'TypeAlias') {
idName = node.id.name;
typeNode = node;
// export type Props = $ReadOnly<{}>
} else if (node.type === 'ExportNamedDeclaration' && node.declaration && node.declaration.type === 'TypeAlias') {
idName = node.declaration.id.name;
typeNode = node.declaration;
}
if (idName) {
foundTypes.push(idName);
if (isReadOnlyType(typeNode)) {
readOnlyTypes.push(idName);
}
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return {
// class components
ClassDeclaration(node) {
if (isReactComponent(node) && isReadOnlyClassProp(node)) {
context.report({
message: node.superTypeParameters.params[0].id.name + ' must be $ReadOnly',
node
});
} else if (node.superTypeParameters && node.superTypeParameters.params[0].type === 'ObjectTypeAnnotation' && !isReadOnlyObjectType(node.superTypeParameters.params[0])) {
context.report({
message: node.id.name + ' class props must be $ReadOnly',
node
});
}
},
// functional components
JSXElement(node) {
var currentNode = node;
var identifier = void 0;
var typeAnnotation = void 0;
while (currentNode && currentNode.type !== 'FunctionDeclaration') {
currentNode = currentNode.parent;
}
// functional components can only have 1 param
if (!currentNode || currentNode.params.length !== 1) {
return;
}
if (currentNode.params[0].type === 'Identifier' && (typeAnnotation = currentNode.params[0].typeAnnotation)) {
if ((identifier = typeAnnotation.typeAnnotation.id) && foundTypes.includes(identifier.name) && !readOnlyTypes.includes(identifier.name) && !reReadOnly.test(identifier.name)) {
if (reportedFunctionalComponents.includes(identifier)) {
return;
}
context.report({
message: identifier.name + ' must be $ReadOnly',
node
});
reportedFunctionalComponents.push(identifier);
return;
}
if (typeAnnotation.typeAnnotation.type === 'ObjectTypeAnnotation' && !isReadOnlyObjectType(typeAnnotation.typeAnnotation)) {
context.report({
message: currentNode.id.name + ' component props must be $ReadOnly',
node
});
}
}
}
};
};
exports.default = {
create,
schema
};
module.exports = exports.default;