diff --git a/src/analyzers/practice/resistor-color/ResistorColorSolution.ts b/src/analyzers/practice/resistor-color/ResistorColorSolution.ts index c0fbf836..c4cdf123 100644 --- a/src/analyzers/practice/resistor-color/ResistorColorSolution.ts +++ b/src/analyzers/practice/resistor-color/ResistorColorSolution.ts @@ -300,7 +300,23 @@ class Entry { return false } - return this.isOptimalHelper(argument, constant) + const result = this.isOptimalHelper(argument, constant) + + if ( + !constant.isOptimalArray && + !constant.isOptimalObject() && + argument && + [ + AST_NODE_TYPES.MemberExpression, + AST_NODE_TYPES.CallExpression, + AST_NODE_TYPES.ObjectExpression, + ].includes(argument.type) + ) { + return true + } + + return result + } public isOptimalHelper(func: Node, constant: Readonly): boolean { diff --git a/src/analyzers/practice/two-fer/index.ts b/src/analyzers/practice/two-fer/index.ts index 6203d186..6466746d 100644 --- a/src/analyzers/practice/two-fer/index.ts +++ b/src/analyzers/practice/two-fer/index.ts @@ -36,6 +36,8 @@ import { import { extractNamedFunction } from '~src/extracts/extract_named_function' import { makeNoSourceOutput } from '~src/output/makeNoSourceOutput' import { makeParseErrorOutput } from '~src/output/makeParseErrorOutput' +import { hasStubThrow } from '~src/analyzers/utils/extract_main_method' +import { REMOVE_STUB_THROW } from '~src/comments/remove_stub_throw' type ConditionalExpression = TSESTree.ConditionalExpression type IfStatement = TSESTree.IfStatement @@ -82,7 +84,7 @@ export class TwoFerAnalyzer extends AnalyzerImpl { private program!: Program private source!: string - private mainMethod!: ExtractedFunction + private mainMethod?: ExtractedFunction protected async execute(input: Input): Promise { const [parsed] = await this.parse(input) @@ -90,11 +92,16 @@ export class TwoFerAnalyzer extends AnalyzerImpl { this.program = parsed.program this.source = parsed.source - this.mainMethod = extractNamedFunction('twoFer', this.program)! + this.mainMethod = extractNamedFunction('twoFer', this.program) // Firstly we want to check that the structure of this solution is correct // and that there is nothing structural stopping it from passing the tests this.checkStructure() + if (!this.mainMethod) return + + if (hasStubThrow(this.mainMethod)) { + this.disapprove(REMOVE_STUB_THROW()) + } // Now we want to ensure that the method signature is sane and that it has // valid arguments diff --git a/src/analyzers/utils/extract_main_method.ts b/src/analyzers/utils/extract_main_method.ts index 6232d55b..65d61dbd 100644 --- a/src/analyzers/utils/extract_main_method.ts +++ b/src/analyzers/utils/extract_main_method.ts @@ -79,3 +79,61 @@ export function extractMainMethod( return undefined } + + +function isNewExpression(node: unknown): node is TSESTree.NewExpression { + return ( + typeof node === 'object' && + node !== null && + (node as TSESTree.Node).type === 'NewExpression' + ) +} + +function isStubThrowStatement(statement: TSESTree.Statement): boolean { + if (statement.type !== 'ThrowStatement') return false + + const argument = statement.argument + if (!isNewExpression(argument)) return false + + const callee = argument.callee + if (callee.type !== 'Identifier' || callee.name !== 'Error') return false + + const [firstArg] = argument.arguments + if (!firstArg || firstArg.type !== 'Literal') return false + if (typeof firstArg.value !== 'string') return false + + return ( + firstArg.value.includes('Please implement') || + firstArg.value.includes('Remove this line and implement') || + firstArg.value.includes('Implement the') || + firstArg.value.includes('Remove this statement and implement') + ) +} + + +export function hasStubThrow(fn: { body?: TSESTree.Node }): boolean { + if (!fn.body || fn.body.type !== 'BlockStatement') return false + + const statements = fn.body.body + + // Case 1: single-line stub throw + if (statements.length === 1 && isStubThrowStatement(statements[0])) { + return true + } + + // Case 2: unreachable stub throw after return + for (let i = 0; i < statements.length - 1; i++) { + const current = statements[i] + const next = statements[i + 1] + + if ( + current.type === 'ReturnStatement' && + isStubThrowStatement(next) + ) { + return true + } + } + + return false +} + diff --git a/src/comments/remove_stub_throw.ts b/src/comments/remove_stub_throw.ts new file mode 100644 index 00000000..616eeb63 --- /dev/null +++ b/src/comments/remove_stub_throw.ts @@ -0,0 +1,5 @@ +import { factory, CommentType } from '~src/comments/comment' + +export const REMOVE_STUB_THROW = factory` +Remove this placeholder throw statement. It is dead code. +`('javascript.general.remove_stub_throw', CommentType.Actionable)