Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support destructuring and aliased imports in react builtin call detec…
…tion (#385) * fix: #380 * feat: Support destructuring and aliased imports in react builtin call detection
- Loading branch information
Showing
9 changed files
with
359 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
*/ | ||
|
||
import { parse } from '../../../tests/utils'; | ||
import isReactCloneElementCall from '../isReactCloneElementCall'; | ||
|
||
describe('isReactCloneElementCall', () => { | ||
function parsePath(src) { | ||
const root = parse(src); | ||
return root.get('body', root.node.body.length - 1, 'expression'); | ||
} | ||
|
||
describe('built in React.createClass', () => { | ||
it('accepts cloneElement called on React', () => { | ||
const def = parsePath(` | ||
var React = require("React"); | ||
React.cloneElement({}); | ||
`); | ||
expect(isReactCloneElementCall(def)).toBe(true); | ||
}); | ||
|
||
it('accepts cloneElement called on aliased React', () => { | ||
const def = parsePath(` | ||
var other = require("React"); | ||
other.cloneElement({}); | ||
`); | ||
expect(isReactCloneElementCall(def)).toBe(true); | ||
}); | ||
|
||
it('ignores other React calls', () => { | ||
const def = parsePath(` | ||
var React = require("React"); | ||
React.isValidElement({}); | ||
`); | ||
expect(isReactCloneElementCall(def)).toBe(false); | ||
}); | ||
|
||
it('ignores non React calls to cloneElement', () => { | ||
const def = parsePath(` | ||
var React = require("bob"); | ||
React.cloneElement({}); | ||
`); | ||
expect(isReactCloneElementCall(def)).toBe(false); | ||
}); | ||
|
||
it('accepts cloneElement called on destructed value', () => { | ||
const def = parsePath(` | ||
var { cloneElement } = require("react"); | ||
cloneElement({}); | ||
`); | ||
expect(isReactCloneElementCall(def)).toBe(true); | ||
}); | ||
|
||
it('accepts cloneElement called on destructed aliased value', () => { | ||
const def = parsePath(` | ||
var { cloneElement: foo } = require("react"); | ||
foo({}); | ||
`); | ||
expect(isReactCloneElementCall(def)).toBe(true); | ||
}); | ||
|
||
it('accepts cloneElement called on imported value', () => { | ||
const def = parsePath(` | ||
import { cloneElement } from "react"; | ||
cloneElement({}); | ||
`); | ||
expect(isReactCloneElementCall(def)).toBe(true); | ||
}); | ||
|
||
it('accepts cloneElement called on imported aliased value', () => { | ||
const def = parsePath(` | ||
import { cloneElement as foo } from "react"; | ||
foo({}); | ||
`); | ||
expect(isReactCloneElementCall(def)).toBe(true); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
*/ | ||
|
||
import { parse } from '../../../tests/utils'; | ||
import isReactCreateElementCall from '../isReactCreateElementCall'; | ||
|
||
describe('isReactCreateElementCall', () => { | ||
function parsePath(src) { | ||
const root = parse(src); | ||
return root.get('body', root.node.body.length - 1, 'expression'); | ||
} | ||
|
||
describe('built in React.createElement', () => { | ||
it('accepts createElement called on React', () => { | ||
const def = parsePath(` | ||
var React = require("React"); | ||
React.createElement({ | ||
render() {} | ||
}); | ||
`); | ||
expect(isReactCreateElementCall(def)).toBe(true); | ||
}); | ||
|
||
it('accepts createElement called on aliased React', () => { | ||
const def = parsePath(` | ||
var other = require("React"); | ||
other.createElement({ | ||
render() {} | ||
}); | ||
`); | ||
expect(isReactCreateElementCall(def)).toBe(true); | ||
}); | ||
|
||
it('ignores other React calls', () => { | ||
const def = parsePath(` | ||
var React = require("React"); | ||
React.isValidElement({}); | ||
`); | ||
expect(isReactCreateElementCall(def)).toBe(false); | ||
}); | ||
|
||
it('ignores non React calls to createElement', () => { | ||
const def = parsePath(` | ||
var React = require("bob"); | ||
React.createElement({ | ||
render() {} | ||
}); | ||
`); | ||
expect(isReactCreateElementCall(def)).toBe(false); | ||
}); | ||
|
||
it('accepts createElement called on destructed value', () => { | ||
const def = parsePath(` | ||
var { createElement } = require("react"); | ||
createElement({}); | ||
`); | ||
expect(isReactCreateElementCall(def)).toBe(true); | ||
}); | ||
|
||
it('accepts createElement called on destructed aliased value', () => { | ||
const def = parsePath(` | ||
var { createElement: foo } = require("react"); | ||
foo({}); | ||
`); | ||
expect(isReactCreateElementCall(def)).toBe(true); | ||
}); | ||
|
||
it('accepts createElement called on imported value', () => { | ||
const def = parsePath(` | ||
import { createElement } from "react"; | ||
createElement({}); | ||
`); | ||
expect(isReactCreateElementCall(def)).toBe(true); | ||
}); | ||
|
||
it('accepts createElement called on imported aliased value', () => { | ||
const def = parsePath(` | ||
import { createElement as foo } from "react"; | ||
foo({}); | ||
`); | ||
expect(isReactCreateElementCall(def)).toBe(true); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
*/ | ||
|
||
import { parse } from '../../../tests/utils'; | ||
import isReactForwardRefCall from '../isReactForwardRefCall'; | ||
|
||
describe('isReactForwardRefCall', () => { | ||
function parsePath(src) { | ||
const root = parse(src); | ||
return root.get('body', root.node.body.length - 1, 'expression'); | ||
} | ||
|
||
describe('built in React.forwardRef', () => { | ||
it('accepts forwardRef called on React', () => { | ||
const def = parsePath(` | ||
var React = require("React"); | ||
React.forwardRef({ | ||
render() {} | ||
}); | ||
`); | ||
expect(isReactForwardRefCall(def)).toBe(true); | ||
}); | ||
|
||
it('accepts forwardRef called on aliased React', () => { | ||
const def = parsePath(` | ||
var other = require("React"); | ||
other.forwardRef({ | ||
render() {} | ||
}); | ||
`); | ||
expect(isReactForwardRefCall(def)).toBe(true); | ||
}); | ||
|
||
it('ignores other React calls', () => { | ||
const def = parsePath(` | ||
var React = require("React"); | ||
React.isValidElement({}); | ||
`); | ||
expect(isReactForwardRefCall(def)).toBe(false); | ||
}); | ||
|
||
it('ignores non React calls to forwardRef', () => { | ||
const def = parsePath(` | ||
var React = require("bob"); | ||
React.forwardRef({ | ||
render() {} | ||
}); | ||
`); | ||
expect(isReactForwardRefCall(def)).toBe(false); | ||
}); | ||
|
||
it('accepts forwardRef called on destructed value', () => { | ||
const def = parsePath(` | ||
var { forwardRef } = require("react"); | ||
forwardRef({}); | ||
`); | ||
expect(isReactForwardRefCall(def)).toBe(true); | ||
}); | ||
|
||
it('accepts forwardRef called on destructed aliased value', () => { | ||
const def = parsePath(` | ||
var { forwardRef: foo } = require("react"); | ||
foo({}); | ||
`); | ||
expect(isReactForwardRefCall(def)).toBe(true); | ||
}); | ||
|
||
it('accepts forwardRef called on imported value', () => { | ||
const def = parsePath(` | ||
import { forwardRef } from "react"; | ||
forwardRef({}); | ||
`); | ||
expect(isReactForwardRefCall(def)).toBe(true); | ||
}); | ||
|
||
it('accepts forwardRef called on imported aliased value', () => { | ||
const def = parsePath(` | ||
import { forwardRef as foo } from "react"; | ||
foo({}); | ||
`); | ||
expect(isReactForwardRefCall(def)).toBe(true); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @flow | ||
*/ | ||
|
||
import types from 'ast-types'; | ||
import isReactModuleName from './isReactModuleName'; | ||
import match from './match'; | ||
import resolveToModule from './resolveToModule'; | ||
import resolveToValue from './resolveToValue'; | ||
|
||
const { namedTypes: t } = types; | ||
|
||
/** | ||
* Returns true if the expression is a function call of the form | ||
* `React.foo(...)`. | ||
*/ | ||
export default function isReactBuiltinCall( | ||
path: NodePath, | ||
name: string, | ||
): boolean { | ||
if (t.ExpressionStatement.check(path.node)) { | ||
path = path.get('expression'); | ||
} | ||
|
||
if (match(path.node, { callee: { property: { name } } })) { | ||
const module = resolveToModule(path.get('callee', 'object')); | ||
return Boolean(module && isReactModuleName(module)); | ||
} | ||
|
||
if (t.CallExpression.check(path.node)) { | ||
const value = resolveToValue(path.get('callee')); | ||
if (value === path.get('callee')) return false; | ||
|
||
if ( | ||
// `require('react').createElement` | ||
(t.MemberExpression.check(value.node) && | ||
t.Identifier.check(value.get('property').node) && | ||
value.get('property').node.name === name) || | ||
// `import { createElement } from 'react'` | ||
(t.ImportDeclaration.check(value.node) && | ||
value.node.specifiers.some( | ||
specifier => specifier.imported && specifier.imported.name === name, | ||
)) | ||
) { | ||
const module = resolveToModule(value); | ||
return Boolean(module && isReactModuleName(module)); | ||
} | ||
} | ||
|
||
return false; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.