-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b039cbc
commit 423956e
Showing
9 changed files
with
409 additions
and
49 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
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,113 @@ | ||
import { RuleTester } from 'eslint'; | ||
import { describe, it } from 'vitest'; | ||
import enforceProxyConfigurationType from './enforce-proxy-configuration-type'; | ||
|
||
const ruleTester = new RuleTester({ | ||
parser: require.resolve('@typescript-eslint/parser'), | ||
parserOptions: { | ||
ecmaVersion: 2018, | ||
sourceType: 'module', | ||
}, | ||
}); | ||
|
||
describe('enforce-proxy-configuration-type', () => { | ||
it('should pass valid cases and fail invalid cases', () => { | ||
ruleTester.run('enforce-proxy-configuration-type', enforceProxyConfigurationType, { | ||
valid: [ | ||
{ | ||
code: ` | ||
const config: ProxyConfiguration = { | ||
endpoint: 'api.xro/2.0/Contacts', | ||
headers: { 'xero-tenant-id': tenant_id }, | ||
params: { summarizeErrors: 'false' }, | ||
data: { Contacts: input.map(toXeroContact) } | ||
}; | ||
const res = await nango.post(config); | ||
`, | ||
}, | ||
{ | ||
code: ` | ||
let config: ProxyConfiguration; | ||
config = { | ||
endpoint: 'api.xro/2.0/Contacts', | ||
headers: { 'xero-tenant-id': tenant_id }, | ||
params: { summarizeErrors: 'false' }, | ||
data: { Contacts: input.map(toXeroContact) } | ||
}; | ||
const res = await nango.get(config); | ||
`, | ||
}, | ||
{ | ||
code: ` | ||
const res = await nango.put({ endpoint: 'api.example.com', data: {} }); | ||
`, | ||
}, | ||
], | ||
invalid: [ | ||
{ | ||
code: ` | ||
const config = { | ||
endpoint: 'api.xro/2.0/Contacts', | ||
headers: { 'xero-tenant-id': tenant_id }, | ||
params: { summarizeErrors: 'false' }, | ||
data: { Contacts: input.map(toXeroContact) } | ||
}; | ||
const res = await nango.post(config); | ||
`, | ||
errors: [{ message: 'Configuration object for Nango API calls should be typed as ProxyConfiguration' }], | ||
output: ` | ||
const config: ProxyConfiguration = { | ||
endpoint: 'api.xro/2.0/Contacts', | ||
headers: { 'xero-tenant-id': tenant_id }, | ||
params: { summarizeErrors: 'false' }, | ||
data: { Contacts: input.map(toXeroContact) } | ||
}; | ||
const res = await nango.post(config); | ||
`, | ||
}, | ||
{ | ||
code: ` | ||
let config = { | ||
endpoint: 'api.xro/2.0/Contacts', | ||
headers: { 'xero-tenant-id': tenant_id }, | ||
params: { summarizeErrors: 'false' }, | ||
data: { Contacts: input.map(toXeroContact) } | ||
}; | ||
const res = await nango.get(config); | ||
`, | ||
errors: [{ message: 'Configuration object for Nango API calls should be typed as ProxyConfiguration' }], | ||
output: ` | ||
let config: ProxyConfiguration = { | ||
endpoint: 'api.xro/2.0/Contacts', | ||
headers: { 'xero-tenant-id': tenant_id }, | ||
params: { summarizeErrors: 'false' }, | ||
data: { Contacts: input.map(toXeroContact) } | ||
}; | ||
const res = await nango.get(config); | ||
`, | ||
}, | ||
{ | ||
code: ` | ||
var config = { | ||
endpoint: 'api.xro/2.0/Contacts', | ||
headers: { 'xero-tenant-id': tenant_id }, | ||
params: { summarizeErrors: 'false' }, | ||
data: { Contacts: input.map(toXeroContact) } | ||
}; | ||
const res = await nango.proxy(config); | ||
`, | ||
errors: [{ message: 'Configuration object for Nango API calls should be typed as ProxyConfiguration' }], | ||
output: ` | ||
var config: ProxyConfiguration = { | ||
endpoint: 'api.xro/2.0/Contacts', | ||
headers: { 'xero-tenant-id': tenant_id }, | ||
params: { summarizeErrors: 'false' }, | ||
data: { Contacts: input.map(toXeroContact) } | ||
}; | ||
const res = await nango.proxy(config); | ||
`, | ||
}, | ||
], | ||
}); | ||
}); | ||
}); |
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,77 @@ | ||
import { Rule } from 'eslint'; | ||
import { Node, CallExpression, Identifier, VariableDeclarator } from 'estree'; | ||
|
||
const enforceProxyConfigurationType: Rule.RuleModule = { | ||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
description: 'Enforce ProxyConfiguration type for Nango API call configurations', | ||
category: 'Best Practices', | ||
recommended: true, | ||
}, | ||
fixable: 'code', | ||
schema: [], | ||
}, | ||
create(context: Rule.RuleContext) { | ||
const sourceCode = context.getSourceCode(); | ||
|
||
return { | ||
CallExpression(node: Node) { | ||
if (isNangoApiCall(node)) { | ||
const options = node.arguments[0]; | ||
|
||
if (options && options.type === 'Identifier') { | ||
const scope = sourceCode.getScope(node); | ||
const variable = scope.variables.find(v => v.name === options.name); | ||
if (variable && variable.defs[0] && variable.defs[0].node.type === 'VariableDeclarator') { | ||
const declarator = variable.defs[0].node; | ||
if (!hasProxyConfigurationType(declarator, context)) { | ||
context.report({ | ||
node: declarator, | ||
message: 'Configuration object for Nango API calls should be typed as ProxyConfiguration', | ||
fix(fixer) { | ||
const declarationToken = sourceCode.getFirstToken(declarator.parent); | ||
if (declarationToken && (declarationToken.value === 'const' || declarationToken.value === 'let' || declarationToken.value === 'var')) { | ||
return fixer.insertTextAfter(declarator.id, ': ProxyConfiguration'); | ||
} | ||
return null; | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
}; | ||
}, | ||
}; | ||
|
||
function isNangoApiCall(node: Node): node is CallExpression { | ||
return ( | ||
node.type === 'CallExpression' && | ||
node.callee.type === 'MemberExpression' && | ||
node.callee.object.type === 'Identifier' && | ||
node.callee.object.name === 'nango' && | ||
node.callee.property.type === 'Identifier' && | ||
['get', 'post', 'put', 'patch', 'delete', 'proxy'].includes(node.callee.property.name) | ||
); | ||
} | ||
|
||
function hasProxyConfigurationType(node: VariableDeclarator, context: Rule.RuleContext): boolean { | ||
const sourceCode = context.getSourceCode(); | ||
const idToken = sourceCode.getFirstToken(node.id); | ||
if (!idToken) return false; | ||
|
||
const nextToken = sourceCode.getTokenAfter(idToken); | ||
if (!nextToken) return false; | ||
|
||
const tokenAfterColon = sourceCode.getTokenAfter(nextToken); | ||
|
||
return ( | ||
nextToken.type === 'Punctuator' && | ||
nextToken.value === ':' && | ||
tokenAfterColon?.value === 'ProxyConfiguration' | ||
); | ||
} | ||
|
||
export default enforceProxyConfigurationType; |
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.