Skip to content

Commit 5a42805

Browse files
committed
add more rules
1 parent 5ce948a commit 5a42805

8 files changed

+377
-1
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nangohq/eslint-plugin-custom-integrations-linting",
3-
"version": "0.0.3",
3+
"version": "0.0.4",
44
"description": "Nango custom integrations ESLint plugin",
55
"main": "dist/index.js",
66
"private": false,

src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@ import noWhileTrue from './rules/no-while-true'
55
import proxyCallRetries from './rules/proxy-call-retries';
66
import enforceProxyConfigurationType from './rules/enforce-proxy-configuration-type';
77
import typeExternalApiResponses from './rules/type-external-api-responses';
8+
import includeDocsForEndpoints from './rules/include-docs-for-endpoints';
9+
import noTryCatchUnlessExplicitlyAllowed from './rules/no-try-catch-unless-explicitly-allowed';
10+
import queryParamsInParamsObject from './rules/query-params-in-params-object';
811

912
export const rules = {
1013
'enforce-proxy-configuration-type': enforceProxyConfigurationType,
1114
'no-console-log': noConsoleLog,
15+
'include-docs-for-endpoints': includeDocsForEndpoints,
1216
'no-object-casting': noObjectCasting,
1317
'no-value-modification': noValueModification,
18+
'no-try-catch-unless-explicitly-allowed': noTryCatchUnlessExplicitlyAllowed,
19+
'query-params-in-params-object': queryParamsInParamsObject,
1420
'no-while-true': noWhileTrue,
1521
'proxy-call-retries': proxyCallRetries,
1622
'type-external-api-responses': typeExternalApiResponses,
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { RuleTester } from 'eslint';
2+
import { describe, it } from 'vitest';
3+
import includeDocsForEndpoints from './include-docs-for-endpoints';
4+
5+
const ruleTester = new RuleTester({
6+
parser: require.resolve('@typescript-eslint/parser'),
7+
parserOptions: {
8+
ecmaVersion: 2018,
9+
sourceType: 'module',
10+
project: './tsconfig.json',
11+
},
12+
});
13+
14+
describe('include-docs-for-endpoints', () => {
15+
it('should enforce documentation URLs for endpoint properties', () => {
16+
ruleTester.run('include-docs-for-endpoints', includeDocsForEndpoints, {
17+
valid: [
18+
{
19+
code: `
20+
const config: ProxyConfiguration = {
21+
// https://developers.zoom.us/docs/api/meetings/#tag/meetings/POST/users/{userId}/meetings
22+
endpoint: '/users/me/meetings',
23+
data: zoomInput,
24+
retries: 10
25+
};
26+
`,
27+
},
28+
{
29+
code: `
30+
const config: ProxyConfiguration = {
31+
// See API docs: https://api.example.com/docs
32+
endpoint: '/api/v1/users',
33+
retries: 10
34+
};
35+
`,
36+
},
37+
{
38+
code: `
39+
const config: ProxyConfiguration = {
40+
/* Documentation: https://api.example.com/docs */
41+
endpoint: '/api/v1/users',
42+
retries: 10
43+
};
44+
`,
45+
},
46+
],
47+
invalid: [
48+
{
49+
code: `
50+
const config: ProxyConfiguration = {
51+
endpoint: '/users/me/meetings',
52+
data: zoomInput,
53+
retries: 10
54+
};
55+
`,
56+
errors: [
57+
{
58+
message: 'Endpoint properties should include a documentation URL in a comment above',
59+
},
60+
],
61+
},
62+
{
63+
code: `
64+
const config: ProxyConfiguration = {
65+
// Configuration for user meetings
66+
endpoint: '/users/me/meetings',
67+
retries: 10
68+
};
69+
`,
70+
errors: [
71+
{
72+
message: 'Endpoint properties should include a documentation URL in a comment above',
73+
},
74+
],
75+
},
76+
],
77+
});
78+
});
79+
});
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { Rule } from 'eslint';
2+
import { Node, ObjectExpression, Property } from 'estree';
3+
4+
const includeDocsForEndpoints: Rule.RuleModule = {
5+
meta: {
6+
type: 'suggestion',
7+
docs: {
8+
description: 'Enforce documentation URLs for endpoint properties in ProxyConfiguration',
9+
category: 'Best Practices',
10+
recommended: true,
11+
},
12+
schema: [],
13+
},
14+
create(context) {
15+
return {
16+
Property(node: Property) {
17+
if (isEndpointProperty(node)) {
18+
const ancestors = context.getAncestors();
19+
const isProxyConfig = ancestors.some(ancestor =>
20+
ancestor.type === 'VariableDeclarator' &&
21+
(ancestor as any).id?.typeAnnotation?.typeAnnotation?.typeName?.name === 'ProxyConfiguration'
22+
);
23+
24+
if (isProxyConfig) {
25+
const sourceCode = context.getSourceCode();
26+
const comments = sourceCode.getCommentsBefore(node);
27+
28+
if (!comments.length || !hasDocumentationUrl(comments)) {
29+
context.report({
30+
node,
31+
message: 'Endpoint properties should include a documentation URL in a comment above',
32+
});
33+
}
34+
}
35+
}
36+
},
37+
};
38+
},
39+
};
40+
41+
function isEndpointProperty(node: Property): boolean {
42+
return (
43+
node.type === 'Property' &&
44+
node.key.type === 'Identifier' &&
45+
node.key.name === 'endpoint'
46+
);
47+
}
48+
49+
function hasDocumentationUrl(comments: any[]): boolean {
50+
return comments.some(comment => {
51+
const commentText = comment.value.trim().toLowerCase();
52+
return (
53+
commentText.includes('http://') ||
54+
commentText.includes('https://') ||
55+
commentText.includes('docs') ||
56+
commentText.includes('api')
57+
);
58+
});
59+
}
60+
61+
export default includeDocsForEndpoints;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { RuleTester } from 'eslint';
2+
import { describe, it } from 'vitest';
3+
import noTryCatchUnlessExplicitlyAllowed from './no-try-catch-unless-explicitly-allowed';
4+
5+
const ruleTester = new RuleTester({
6+
parser: require.resolve('@typescript-eslint/parser'),
7+
parserOptions: { ecmaVersion: 2018, sourceType: 'module' },
8+
});
9+
10+
describe('no-try-catch-unless-explicitly-allowed', () => {
11+
it('should enforce try-catch restrictions', () => {
12+
ruleTester.run('no-try-catch-unless-explicitly-allowed', noTryCatchUnlessExplicitlyAllowed, {
13+
valid: [
14+
{
15+
code: `
16+
// @allowTryCatch
17+
try {
18+
await nango.get({});
19+
} catch (error) {
20+
console.error(error);
21+
}
22+
`,
23+
},
24+
{
25+
code: `
26+
/* @allowTryCatch */
27+
try {
28+
await nango.post({});
29+
} catch (error) {
30+
console.error(error);
31+
}
32+
`,
33+
},
34+
],
35+
invalid: [
36+
{
37+
code: `
38+
try {
39+
await nango.get({});
40+
} catch (error) {
41+
console.error(error);
42+
}
43+
`,
44+
errors: [{ message: 'Try-catch blocks are not allowed unless explicitly marked with @allowTryCatch comment. It is best practice to allow the Nango platform to handle errors. Override this if you need to handle errors explicitly with special cases.' }],
45+
},
46+
],
47+
});
48+
});
49+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Rule } from 'eslint';
2+
import { Node, TryStatement } from 'estree';
3+
4+
const noTryCatchUnlessExplicitlyAllowed: Rule.RuleModule = {
5+
meta: {
6+
type: 'problem',
7+
docs: {
8+
description: 'Disallow try-catch blocks unless explicitly allowed via comments',
9+
category: 'Best Practices',
10+
recommended: true,
11+
},
12+
schema: [],
13+
},
14+
create(context) {
15+
return {
16+
TryStatement(node: TryStatement) {
17+
const sourceCode = context.getSourceCode();
18+
const comments = sourceCode.getCommentsBefore(node);
19+
20+
const isAllowed = comments.some(comment =>
21+
comment.value.trim().toLowerCase().includes('@allowtrycatch')
22+
);
23+
24+
if (!isAllowed) {
25+
context.report({
26+
node,
27+
message: 'Try-catch blocks are not allowed unless explicitly marked with @allowTryCatch comment. It is best practice to allow the Nango platform to handle errors. Override this if you need to handle errors explicitly with special cases.',
28+
});
29+
}
30+
},
31+
};
32+
},
33+
};
34+
35+
export default noTryCatchUnlessExplicitlyAllowed;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { RuleTester } from 'eslint';
2+
import { describe, it } from 'vitest';
3+
import queryParamsInParamsObject from './query-params-in-params-object';
4+
5+
const ruleTester = new RuleTester({
6+
parser: require.resolve('@typescript-eslint/parser'),
7+
parserOptions: { ecmaVersion: 2018, sourceType: 'module' },
8+
});
9+
10+
describe('query-params-in-params-object', () => {
11+
it('should enforce query parameters in params object', () => {
12+
ruleTester.run('query-params-in-params-object', queryParamsInParamsObject, {
13+
valid: [
14+
{
15+
code: `
16+
await nango.get({
17+
endpoint: 'api/users',
18+
params: {
19+
page: '1',
20+
limit: '10'
21+
}
22+
});
23+
`,
24+
},
25+
],
26+
invalid: [
27+
{
28+
code: `
29+
await nango.get({
30+
endpoint: 'api/users?page=1&limit=10'
31+
});
32+
`,
33+
errors: [{ message: 'Query parameters should be in the params object instead of the endpoint URL' }],
34+
output: `
35+
await nango.get({
36+
endpoint: 'api/users',
37+
params: {
38+
"page": "1",
39+
"limit": "10"
40+
}
41+
});
42+
`,
43+
},
44+
],
45+
});
46+
});
47+
});

0 commit comments

Comments
 (0)