diff --git a/README.md b/README.md index c3e239e..6ca11d4 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,14 @@ class ClassName { } ``` +Platform-specific suffixes (e.g. `.android.ts`, `.ios.ts`) are stripped from the filename, so `SomeClass.android.ts` expects `SomeClass` in the logging, not `SomeClass.android`: +```typescript +// SomeClass.android.ts +function functionName(){ + Log.trace('SomeClass:functionName'); +} +``` + ## Configuration By default the rule applies to all functions. You can optionally provide an `ignoreList` list of regex patterns — any function whose name matches a pattern is exempt from the rule. diff --git a/dist/rules/__tests__/no-function-without-logging.test.js b/dist/rules/__tests__/no-function-without-logging.test.js index 835d4f5..e40e37f 100644 --- a/dist/rules/__tests__/no-function-without-logging.test.js +++ b/dist/rules/__tests__/no-function-without-logging.test.js @@ -66,6 +66,11 @@ ruleTester.run("no-function-without-logging", no_function_without_logging_1.defa filename: "Component", code: "const Component = () => { Log.debug('Component') }", }, + { + name: "Platform-specific suffix is stripped from filename in logging", + filename: "SomeClass.android.ts", + code: "function functionName(){ Log.debug('SomeClass:functionName') }", + }, { name: "Ignored function declaration is skipped", options: [{ ignoreList: ["ignoredFunction"] }], @@ -208,6 +213,45 @@ ruleTester.run("no-function-without-logging", no_function_without_logging_1.defa }, ], }, + { + name: "Missing logging in function declaration in platform-specific file", + filename: "SomeClass.android.ts", + code: "function functionName(){}", + errors: [ + { + messageId: "missingLogging", + data: { expectedLogging: "SomeClass:functionName" }, + suggestions: [ + { + messageId: "addLoggingSuggestion", + data: { suggestedCode: "Log.trace('SomeClass:functionName');" }, + output: "function functionName(){Log.trace('SomeClass:functionName');}", + }, + { + messageId: "addLoggingSuggestion", + data: { suggestedCode: "Log.debug('SomeClass:functionName');" }, + output: "function functionName(){Log.debug('SomeClass:functionName');}", + }, + ], + }, + ], + }, + { + name: "Incorrect logging using full filename with platform suffix", + filename: "SomeClass.android.ts", + code: "function functionName(){ Log.debug('SomeClass.android:functionName') }", + errors: [ + { + messageId: "incorrectLogging", + suggestions: [ + { + messageId: "incorrectLogging", + output: "function functionName(){ Log.debug('SomeClass:functionName') }", + }, + ], + }, + ], + }, { name: "Missing function name in logging", code: "function functionName(){ Log.debug('file') }", diff --git a/dist/rules/no-function-without-logging.js b/dist/rules/no-function-without-logging.js index 8bfd402..47769b4 100644 --- a/dist/rules/no-function-without-logging.js +++ b/dist/rules/no-function-without-logging.js @@ -38,6 +38,11 @@ const path = __importStar(require("path")); const utils_1 = require("@typescript-eslint/utils"); const utils_2 = require("../utils"); const createRule = utils_1.ESLintUtils.RuleCreator(() => "https://github.com/observation/eslint-rules"); +const getClassName = (filename) => { + const base = path.basename(filename); + const firstDotIndex = base.indexOf("."); + return firstDotIndex === -1 ? base : base.slice(0, firstDotIndex); +}; const isIgnored = (functionName, patterns) => { if (!functionName || patterns.length === 0) return false; @@ -120,15 +125,15 @@ const checkFunctionDeclaration = (context, node, ignoreList) => { const functionName = node.id ? node.id.name : ""; if (isIgnored(functionName, ignoreList)) return; - const file = path.parse(context.getFilename()); - const correctLogging = `${file.name}:${functionName}`; + const className = getClassName(context.getFilename()); + const correctLogging = `${className}:${functionName}`; if (!containsLoggingStatement(node.body)) { addMissingLogStatementSuggestions(context, node, node.body, correctLogging); } }; const checkCallExpression = (context, node, ignoreList) => { if (isLogStatement(node)) { - const filename = path.parse(context.getFilename()).name; + const filename = getClassName(context.getFilename()); const functionName = getFunctionName(node); if (isIgnored(functionName, ignoreList)) return; @@ -181,7 +186,7 @@ const checkVariableDeclaration = (context, node, ignoreList) => { (0, utils_2.isBlockStatement)(declaration.init.body) && (0, utils_2.isIdentifier)(declaration.id)) { const { body } = declaration.init; - const filename = path.parse(context.getFilename()).name; + const filename = getClassName(context.getFilename()); const functionName = declaration.id.name; const isComponentDeclaration = filename === functionName; if (isComponentDeclaration) @@ -200,7 +205,7 @@ const checkPropertyDefinition = (context, node, ignoreList) => { (0, utils_2.isIdentifier)(node.key) && (0, utils_2.isBlockStatement)(node.value.body)) { const { body } = node.value; - const filename = path.parse(context.getFilename()).name; + const filename = getClassName(context.getFilename()); const functionName = node.key.name; if (isIgnored(functionName, ignoreList)) return; @@ -227,7 +232,7 @@ const checkMethodDefinition = (context, node, ignoreList) => { return; if ((0, utils_2.isFunctionExpression)(node.value) && (0, utils_2.isIdentifier)(node.key)) { const { body } = node.value; - const filename = path.parse(context.getFilename()).name; + const filename = getClassName(context.getFilename()); const functionName = node.key.name; if (isSetterLikeMethodDefinition(node, functionName)) return; diff --git a/src/rules/__tests__/no-function-without-logging.test.ts b/src/rules/__tests__/no-function-without-logging.test.ts index 9df34c6..2dfaf60 100644 --- a/src/rules/__tests__/no-function-without-logging.test.ts +++ b/src/rules/__tests__/no-function-without-logging.test.ts @@ -63,6 +63,11 @@ ruleTester.run("no-function-without-logging", rule, { filename: "Component", code: "const Component = () => { Log.debug('Component') }", }, + { + name: "Platform-specific suffix is stripped from filename in logging", + filename: "SomeClass.android.ts", + code: "function functionName(){ Log.debug('SomeClass:functionName') }", + }, { name: "Ignored function declaration is skipped", options: [{ ignoreList: ["ignoredFunction"] }], @@ -215,6 +220,48 @@ ruleTester.run("no-function-without-logging", rule, { }, ], }, + { + name: "Missing logging in function declaration in platform-specific file", + filename: "SomeClass.android.ts", + code: "function functionName(){}", + errors: [ + { + messageId: "missingLogging", + data: { expectedLogging: "SomeClass:functionName" }, + suggestions: [ + { + messageId: "addLoggingSuggestion", + data: { suggestedCode: "Log.trace('SomeClass:functionName');" }, + output: + "function functionName(){Log.trace('SomeClass:functionName');}", + }, + { + messageId: "addLoggingSuggestion", + data: { suggestedCode: "Log.debug('SomeClass:functionName');" }, + output: + "function functionName(){Log.debug('SomeClass:functionName');}", + }, + ], + }, + ], + }, + { + name: "Incorrect logging using full filename with platform suffix", + filename: "SomeClass.android.ts", + code: "function functionName(){ Log.debug('SomeClass.android:functionName') }", + errors: [ + { + messageId: "incorrectLogging", + suggestions: [ + { + messageId: "incorrectLogging", + output: + "function functionName(){ Log.debug('SomeClass:functionName') }", + }, + ], + }, + ], + }, { name: "Missing function name in logging", code: "function functionName(){ Log.debug('file') }", diff --git a/src/rules/no-function-without-logging.ts b/src/rules/no-function-without-logging.ts index b266f16..44cd331 100644 --- a/src/rules/no-function-without-logging.ts +++ b/src/rules/no-function-without-logging.ts @@ -24,6 +24,12 @@ type messageIds = "incorrectLogging" | "missingLogging" | "addLoggingSuggestion" type Options = [{ ignoreList?: string[] }] +const getClassName = (filename: string): string => { + const base = path.basename(filename) + const firstDotIndex = base.indexOf(".") + return firstDotIndex === -1 ? base : base.slice(0, firstDotIndex) +} + const isIgnored = (functionName: string | null, patterns: string[]): boolean => { if (!functionName || patterns.length === 0) return false return patterns.some((pattern) => new RegExp(pattern).test(functionName)) @@ -137,9 +143,9 @@ const checkFunctionDeclaration = ( const functionName = node.id ? node.id.name : "" if (isIgnored(functionName, ignoreList)) return - const file = path.parse(context.getFilename()) + const className = getClassName(context.getFilename()) - const correctLogging = `${file.name}:${functionName}` + const correctLogging = `${className}:${functionName}` if (!containsLoggingStatement(node.body)) { addMissingLogStatementSuggestions(context, node, node.body, correctLogging) } @@ -151,7 +157,7 @@ const checkCallExpression = ( ignoreList: string[] ) => { if (isLogStatement(node)) { - const filename = path.parse(context.getFilename()).name + const filename = getClassName(context.getFilename()) const functionName = getFunctionName(node) if (isIgnored(functionName, ignoreList)) return const expectedLogging = filename === functionName ? filename : `${filename}:${functionName}` @@ -219,7 +225,7 @@ const checkVariableDeclaration = ( ) { const { body } = declaration.init - const filename = path.parse(context.getFilename()).name + const filename = getClassName(context.getFilename()) const functionName = declaration.id.name const isComponentDeclaration = filename === functionName @@ -245,7 +251,7 @@ const checkPropertyDefinition = ( isBlockStatement(node.value.body) ) { const { body } = node.value - const filename = path.parse(context.getFilename()).name + const filename = getClassName(context.getFilename()) const functionName = node.key.name if (isIgnored(functionName, ignoreList)) return @@ -284,7 +290,7 @@ const checkMethodDefinition = ( if (isFunctionExpression(node.value) && isIdentifier(node.key)) { const { body } = node.value - const filename = path.parse(context.getFilename()).name + const filename = getClassName(context.getFilename()) const functionName = node.key.name if (isSetterLikeMethodDefinition(node, functionName)) return