formatjs / formatjs-old

The monorepo home to all of the FormatJS related libraries.
https://formatjs.io/
156 stars 53 forks source link

Detect destructured formatMessage usage #529

Closed fsmaia closed 4 years ago

fsmaia commented 4 years ago

Which package? babel-plugin-react-intl

Is your feature request related to a problem? Please describe. When using formatMessage by destructuring intl, like in the following snippet, it isn't detected by the plugin:

const { formatMessage } = intl;
formatMessage({ id: 'id' });

Describe the solution you'd like Although intrusive, we could check for all the formatMessage callees, as a kind of reserved word.

Describe alternatives you've considered

fsmaia commented 4 years ago

A way to achieve it would be performing the following verification inside isFormatMessageCall:

callee.isIdentifier() && callee.node.name === 'formatMessage'
fsmaia commented 4 years ago

To improve the rule, we could check the callee scope to find expressions like:

const { intl: { formatMessage } } = this.props;
const { formatMessage } = intl;
fsmaia commented 4 years ago

Any suggestions?

longlho commented 4 years ago

it's technically possible you would need to make sure references are kept track of properly, e.g const {formatMessage: f} = intl

PR's always welcome :) along w/ test cases to demo your use case

fsmaia commented 4 years ago

I solved the biggest part of the problem, by detecting const { intl: { formatMessage } } = this.props in class components, and destructured usaged in stateless components, with the following excerpt:

function isFormatMessageDestructuring(scope: Scope) {
  const binding = scope.getBinding('formatMessage');
  const block = scope.block as t.FunctionDeclaration;

  if (binding && t.isVariableDeclarator(binding.path.node)) {
    const nodeObject = binding.path.node.id as ObjectPattern;
    return nodeObject.properties.find(
      (value: any) => value.key.name === 'intl'
    );
  }

  if (t.isObjectPattern(block.params[0])) {
    return block.params[0].properties.find(
      (value: any) => value.key.name === 'intl'
    );
  }

  return false;
}

function isFormatMessageCall(
  callee: NodePath<Expression | V8IntrinsicIdentifier>,
  path: any
) {
  if (
    callee.isIdentifier() &&
    callee.node.name === 'formatMessage' &&
    isFormatMessageDestructuring(path.scope)
  ) {
    return true;
  }

  //..

Now I'm missing const { props: { intl: { formatMessage } } } = this pattern.

So close :)

longlho commented 4 years ago

you prob will have to handle aliasing as well, e.g {formatMesage: _} = intl or something like that