-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
[New] Component detection: add util.isReactHookCall
#3156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| 'use strict'; | ||
|
|
||
| const assert = require('assert'); | ||
| const entries = require('object.entries'); | ||
| const eslint = require('eslint'); | ||
| const fromEntries = require('object.fromentries'); | ||
| const values = require('object.values'); | ||
|
|
||
| const Components = require('../../lib/util/Components'); | ||
|
|
@@ -19,12 +21,32 @@ const ruleTester = new eslint.RuleTester({ | |
|
|
||
| describe('Components', () => { | ||
| describe('static detect', () => { | ||
| function testComponentsDetect(test, done) { | ||
| const rule = Components.detect((context, components, util) => ({ | ||
| 'Program:exit'() { | ||
| done(context, components, util); | ||
| }, | ||
| })); | ||
| function testComponentsDetect(test, instructionsOrDone, orDone) { | ||
| const done = orDone || instructionsOrDone; | ||
| const instructions = orDone ? instructionsOrDone : instructionsOrDone; | ||
|
|
||
| const rule = Components.detect((_context, components, util) => { | ||
| const instructionResults = []; | ||
|
|
||
| const augmentedInstructions = fromEntries( | ||
| entries(instructions || {}).map((nodeTypeAndHandler) => { | ||
| const nodeType = nodeTypeAndHandler[0]; | ||
| const handler = nodeTypeAndHandler[1]; | ||
| return [nodeType, (node) => { | ||
| instructionResults.push({ type: nodeType, result: handler(node, context, components, util) }); | ||
|
Comment on lines
+31
to
+36
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'm a bit confused about these changes; could you walk me through why they're needed?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The purpose of the My original implementation exfiltrated the These changes are intended to aid in writing new test cases for Custom instructions passed into the
TodayTaken together, these allow creation of test cases intended to read something like:
TomorrowDown the road, as we work to flesh out coverage for the code in
|
||
| }]; | ||
| }) | ||
| ); | ||
|
|
||
| return Object.assign({}, augmentedInstructions, { | ||
| 'Program:exit'(node) { | ||
| if (augmentedInstructions['Program:exit']) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't yet exercised, but I figured if someone needed to test
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a self-test section to the |
||
| augmentedInstructions['Program:exit'](node, context, components, util); | ||
| } | ||
| done(components, instructionResults); | ||
| }, | ||
| }); | ||
| }); | ||
|
|
||
| const tests = { | ||
| valid: parsers.all([Object.assign({}, test, { | ||
|
|
@@ -36,6 +58,7 @@ describe('Components', () => { | |
| })]), | ||
| invalid: [], | ||
| }; | ||
|
|
||
| ruleTester.run(test.code, rule, tests); | ||
| } | ||
|
|
||
|
|
@@ -45,7 +68,7 @@ describe('Components', () => { | |
| function MyStatelessComponent() { | ||
| return <React.Fragment />; | ||
| }`, | ||
| }, (_context, components) => { | ||
| }, (components) => { | ||
| assert.equal(components.length(), 1, 'MyStatelessComponent should be detected component'); | ||
| values(components.list()).forEach((component) => { | ||
| assert.equal( | ||
|
|
@@ -65,7 +88,7 @@ describe('Components', () => { | |
| return <React.Fragment />; | ||
| } | ||
| }`, | ||
| }, (_context, components) => { | ||
| }, (components) => { | ||
| assert(components.length() === 1, 'MyClassComponent should be detected component'); | ||
| values(components.list()).forEach((component) => { | ||
| assert.equal( | ||
|
|
@@ -80,7 +103,7 @@ describe('Components', () => { | |
| it('should detect React Imports', () => { | ||
| testComponentsDetect({ | ||
| code: 'import React, { useCallback, useState } from \'react\'', | ||
| }, (_context, components) => { | ||
| }, (components) => { | ||
| assert.deepEqual( | ||
| components.getDefaultReactImports().map((specifier) => specifier.local.name), | ||
| ['React'], | ||
|
|
@@ -94,5 +117,186 @@ describe('Components', () => { | |
| ); | ||
| }); | ||
| }); | ||
|
|
||
| describe('utils', () => { | ||
| describe('isReactHookCall', () => { | ||
| it('should not identify hook-like call', () => { | ||
| testComponentsDetect({ | ||
| code: `import { useRef } from 'react' | ||
| function useColor() { | ||
| return useState() | ||
| }`, | ||
| }, { | ||
| CallExpression: (node, _context, _components, util) => util.isReactHookCall(node), | ||
| }, (_components, instructionResults) => { | ||
| assert.deepEqual(instructionResults, [{ type: 'CallExpression', result: false }]); | ||
| }); | ||
| }); | ||
|
|
||
| it('should identify hook call', () => { | ||
| testComponentsDetect({ | ||
| code: `import { useState } from 'react' | ||
| function useColor() { | ||
| return useState() | ||
| }`, | ||
| }, { | ||
| CallExpression: (node, _context, _components, util) => util.isReactHookCall(node), | ||
| }, (_components, instructionResults) => { | ||
| assert.deepEqual(instructionResults, [{ type: 'CallExpression', result: true }]); | ||
| }); | ||
| }); | ||
|
|
||
| it('should identify aliased hook call', () => { | ||
| testComponentsDetect({ | ||
| code: `import { useState as useStateAlternative } from 'react' | ||
| function useColor() { | ||
| return useStateAlternative() | ||
| }`, | ||
| }, { | ||
| CallExpression: (node, _context, _components, util) => util.isReactHookCall(node), | ||
| }, (_components, instructionResults) => { | ||
| assert.deepEqual(instructionResults, [{ type: 'CallExpression', result: true }]); | ||
| }); | ||
| }); | ||
|
|
||
| it('should identify aliased present named hook call', () => { | ||
| testComponentsDetect({ | ||
| code: `import { useState as useStateAlternative } from 'react' | ||
| function useColor() { | ||
| return useStateAlternative() | ||
| }`, | ||
| }, { | ||
| CallExpression: (node, _context, _components, util) => util.isReactHookCall(node, ['useState']), | ||
| }, (_components, instructionResults) => { | ||
| assert.deepEqual(instructionResults, [{ type: 'CallExpression', result: true }]); | ||
| }); | ||
| }); | ||
|
|
||
| it('should not identify shadowed hook call', () => { | ||
| testComponentsDetect({ | ||
| code: `import { useState } from 'react' | ||
| function useColor() { | ||
| function useState() { | ||
| return null | ||
| } | ||
| return useState() | ||
| }`, | ||
| }, { | ||
| CallExpression: (node, _context, _components, util) => util.isReactHookCall(node), | ||
| }, (_components, instructionResults) => { | ||
| assert.deepEqual(instructionResults, [{ type: 'CallExpression', result: false }]); | ||
| }); | ||
| }); | ||
|
|
||
| it('should not identify shadowed aliased present named hook call', () => { | ||
| testComponentsDetect({ | ||
| code: `import { useState as useStateAlternative } from 'react' | ||
| function useColor() { | ||
| function useStateAlternative() { | ||
| return null | ||
| } | ||
| return useStateAlternative() | ||
| }`, | ||
| }, { | ||
| CallExpression: (node, _context, _components, util) => util.isReactHookCall(node, ['useState']), | ||
| }, (_components, instructionResults) => { | ||
| assert.deepEqual(instructionResults, [{ type: 'CallExpression', result: false }]); | ||
| }); | ||
| }); | ||
|
|
||
| it('should identify React hook call', () => { | ||
| testComponentsDetect({ | ||
| code: `import React from 'react' | ||
| function useColor() { | ||
| return React.useState() | ||
| }`, | ||
| }, { | ||
| CallExpression: (node, _context, _components, util) => util.isReactHookCall(node), | ||
| }, (_components, instructionResults) => { | ||
| assert.deepEqual(instructionResults, [{ type: 'CallExpression', result: true }]); | ||
| }); | ||
| }); | ||
|
|
||
| it('should identify aliased React hook call', () => { | ||
| testComponentsDetect({ | ||
| code: `import ReactAlternative from 'react' | ||
| function useColor() { | ||
| return ReactAlternative.useState() | ||
| }`, | ||
| }, { | ||
| CallExpression: (node, _context, _components, util) => util.isReactHookCall(node), | ||
| }, (_components, instructionResults) => { | ||
| assert.deepEqual(instructionResults, [{ type: 'CallExpression', result: true }]); | ||
| }); | ||
| }); | ||
|
|
||
| it('should not identify shadowed React hook call', () => { | ||
| testComponentsDetect({ | ||
| code: `import React from 'react' | ||
| function useColor() { | ||
| const React = { | ||
| useState: () => null | ||
| } | ||
| return React.useState() | ||
| }`, | ||
| }, { | ||
| CallExpression: (node, _context, _components, util) => util.isReactHookCall(node), | ||
| }, (_components, instructionResults) => { | ||
| assert.deepEqual(instructionResults, [{ type: 'CallExpression', result: false }]); | ||
| }); | ||
| }); | ||
|
|
||
| it('should identify present named hook call', () => { | ||
| testComponentsDetect({ | ||
| code: `import { useState } from 'react' | ||
| function useColor() { | ||
| return useState() | ||
| }`, | ||
| }, { | ||
| CallExpression: (node, _context, _components, util) => util.isReactHookCall(node, ['useState']), | ||
| }, (_components, instructionResults) => { | ||
| assert.deepEqual(instructionResults, [{ type: 'CallExpression', result: true }]); | ||
| }); | ||
| }); | ||
|
|
||
| it('should identify present named React hook call', () => { | ||
| testComponentsDetect({ | ||
| code: `import React from 'react' | ||
| function useColor() { | ||
| return React.useState() | ||
| }`, | ||
| }, { | ||
| CallExpression: (node, _context, _components, util) => util.isReactHookCall(node, ['useState']), | ||
| }, (_components, instructionResults) => { | ||
| assert.deepEqual(instructionResults, [{ type: 'CallExpression', result: true }]); | ||
| }); | ||
| }); | ||
|
|
||
| it('should not identify missing named hook call', () => { | ||
| testComponentsDetect({ | ||
| code: `import { useState } from 'react' | ||
| function useColor() { | ||
| return useState() | ||
| }`, | ||
| }, { | ||
| CallExpression: (node, _context, _components, util) => util.isReactHookCall(node, ['useRef']), | ||
| }, (_components, instructionResults) => { | ||
| assert.deepEqual(instructionResults, [{ type: 'CallExpression', result: false }]); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| describe('testComponentsDetect', () => { | ||
| it('should log Program:exit instruction', () => { | ||
| testComponentsDetect({ | ||
| code: '', | ||
| }, { | ||
| 'Program:exit': () => true, | ||
| }, (_components, instructionResults) => { | ||
| assert.deepEqual(instructionResults, [{ type: 'Program:exit', result: true }]); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.