From b18b5a5c1a9eb3ae57ad76cd08eeb4469104fcc9 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 3 Jan 2023 15:51:37 +0100 Subject: [PATCH] add new local eslint rule to catch direct usage of resources without tryExecuteAndNotify --- src/Umbraco.Web.UI.Client/.eslintrc.json | 3 +- .../eslint-local-rules.cjs | 38 +++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/.eslintrc.json b/src/Umbraco.Web.UI.Client/.eslintrc.json index 4cd0e150db..5034e34797 100644 --- a/src/Umbraco.Web.UI.Client/.eslintrc.json +++ b/src/Umbraco.Web.UI.Client/.eslintrc.json @@ -33,7 +33,8 @@ "no-var": "error", "import/no-unresolved": "error", "import/order": "warn", - "local-rules/bad-type-import": "error" + "local-rules/bad-type-import": "error", + "local-rules/no-direct-api-import": "warn" }, "settings": { "import/parsers": { diff --git a/src/Umbraco.Web.UI.Client/eslint-local-rules.cjs b/src/Umbraco.Web.UI.Client/eslint-local-rules.cjs index eeddac50c8..bae4cd16b0 100644 --- a/src/Umbraco.Web.UI.Client/eslint-local-rules.cjs +++ b/src/Umbraco.Web.UI.Client/eslint-local-rules.cjs @@ -4,11 +4,11 @@ * A eslint rule that ensures the use of the `import type` operator from the `src/core/models/index.ts` file. */ // eslint-disable-next-line no-undef -/** @type {import('eslint').Rule.RuleModule} */ module.exports = { + /** @type {import('eslint').Rule.RuleModule} */ 'bad-type-import': { meta: { - type: 'problem', + type: 'suggestion', docs: { description: 'Ensures the use of the `import type` operator from the `src/core/models/index.ts` file.', category: 'Best Practices', @@ -20,7 +20,7 @@ module.exports = { create: function (context) { return { ImportDeclaration: function (node) { - if (node.source.parent.importKind !== 'type' && (node.source.value.endsWith('/models') || node.source.value.endsWith('/generated-schema') || node.source.value === 'router-slot/model')) { + if (node.source.parent.importKind !== 'type' && (node.source.value.endsWith('/models') || node.source.value === 'router-slot/model')) { const sourceCode = context.getSourceCode(); const nodeSource = sourceCode.getText(node); context.report({ @@ -32,5 +32,37 @@ module.exports = { }, }; } + }, + + /** @type {import('eslint').Rule.RuleModule} */ + 'no-direct-api-import': { + meta: { + type: 'suggestion', + docs: { + description: 'Ensures that any API resources from the `@umbraco-cms/backend-api` module are not used directly. Instead you should use the `tryExecuteAndNotify` function from the `@umbraco-cms/resources` module.', + category: 'Best Practices', + recommended: true + }, + fixable: 'code', + schema: [], + }, + create: function (context) { + return { + // If methods called on *Resource classes are not already wrapped with `await tryExecuteAndNotify()`, then we should suggest to wrap them. + CallExpression: function (node) { + if (node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier' && node.callee.object.name.endsWith('Resource') && node.callee.property.type === 'Identifier' && node.callee.property.name !== 'constructor') { + const hasTryExecuteAndNotify = node.parent.parent.type === 'AwaitExpression' && node.parent.callee.name === 'tryExecuteAndNotify'; + if (!hasTryExecuteAndNotify) { + context.report({ + node, + message: 'Wrap this call with `tryExecuteAndNotify()`. Make sure to `await` the result.', + fix: fixer => [fixer.insertTextBefore(node, 'tryExecuteAndNotify(this, '), fixer.insertTextAfter(node, ')')], + }); + } + } + } + }; + + }, } };