ek9_extract_method

Inserts the new method (parameters and return inferred from variable data-flow) and replaces the block with a call to it; answers status, edits[] and summary, an ambiguous or boundary-crossing block as conflicts[].

Category: Editor · Compare: Refactor → Extract → Method (IntelliJ) / LSP Extract Method

When to use it

'Pull this block of a method, function or program into computeSubtotal' is one call with file, fromText and toText (the block's first and last line as written - no line counting) and name; parameters and the return are inferred, and copies of the block in the same body become calls too.

When not to use it

Details

Parameters = outer-scope vars read/written in the block; return = a var produced in the block and read after (or the enclosing return var written in the block); 'as pure' whenever the new member compiles pure - the compiler's purity rules decide (printing through Stdout is pure in EK9; mutation and non-pure calls are not) - and always from a pure source member. Conflict kinds (all mechanical): 'return-type-ambiguous' (≥2 outflows, supply multiReturn=formal-record|formal-class|dynamic-class, AI-chosen from downstream intent), 'block-spans-control-flow' (multi-exit selection - narrow to one scope), 'variable-flows-in-and-out' (in-out accumulator - EK9 forbids param reassignment; narrow so the var is only-consumed or only-produced), 'extracted-block-references-outer-state'. V1 scope: member methods + top-level functions; containerName defaults to <Name>Result. A selection ending in the enclosing member's return line returns that member's whole result - the summary says so with the range without it; returnType:'Integer' takes that range when it returns the type asked for (corrected[]), else refuses (return-type-not-produced).

Arguments

ArgumentTypeRequiredWhat it is
containerNamestringOptional name for the synthesized container type (PascalCase). Defaults to <Name>Result.
endLineintegerOr the last line of the block (inclusive, 1-based). Same body as startLine.
filestringyesThe .ek9 source file (workspace-relative; an absolute path inside the workspace is accepted).
fromTextstringThe block's FIRST line exactly as written (indentation ignored) - instead of counting startLine. Must name one line; add more of it when it occurs twice.
multiReturnstring: formal-record | formal-class | dynamic-classWhen ≥2 values flow out of the block, the container shape that packages them into ONE return: 'formal-record' (top-level record, public fields, call site reads bundle.field), 'formal-class' (top-level class with accessor methods, bundle.field()), or 'dynamic-class' (inline captured class, no separate construct). Omit for a single value out; ≥2 out without it → return-type-ambiguous conflict listing these options. AI-directed: pick the shape from downstream intent (incidental bundle vs data carrier vs domain type).
namestringyesName for the new method (camelCase).
replaceDuplicatesbooleanDefault true: every other copy of the selected statements in the same body that is PROVEN the same computation (same shape, names mapped one-to-one, same types, only the same value flowing out) is replaced with the call too; near misses are listed in duplicates[] with the reason, never replaced.
returnTypestringOptional: the type the new member returns ('Integer'). A selection ending in the enclosing member's 'rtn: ...' line returns that member's whole result; when that is not the type asked for, the one other reading - those trailing return lines stay where they are - is taken if it returns the type asked for (said in corrected[]), else refused naming what each reading returns.
startLineintegerOr the first line of the block (inclusive, 1-based) - an IDE selection. Inside a method, function or program body.
toTextstringThe block's LAST line as written - the first line matching it at or after fromText. Omit it for a one-line block.

It also takes the arguments every tool takes.

Recorded calls

Real calls, recorded against small projects and checked on every build - a changed answer fails it. 3 of the 12 recorded are shown.

Misaligned selection names whole statements

Request

{
  "endLine" : 19,
  "file" : "task.ek9",
  "name" : "total",
  "startLine" : 16
}

Response

{
  "conflicts" : [ {
    "kind" : "block-spans-control-flow",
    "message" : "The selection 16-19 does not align with whole statements - it starts above the body, whose statements begin on line 18. The statements it touches are lines 18-20: re-issue with startLine:18, endLine:20 to extract them.",
    "suggestedRange" : "18-20"
  } ],
  "edits" : [ ],
  "meta" : {
    "serverMs" : "<timing>"
  },
  "risk" : {
    "crossesModuleBoundary" : false,
    "estimatedSeverity" : "trivial",
    "fileCount" : 0,
    "lineCount" : 0,
    "touchesPublicApi" : false,
    "touchesTests" : false
  },
  "status" : "conflicts",
  "summary" : "Selection crosses a statement / control-flow boundary",
  "workspaceHash" : "<workspace-hash>",
  "wouldBeDiagnostics" : [ ],
  "written" : false
}

Selection from blank line before method

Request

{
  "endLine" : 18,
  "file" : "task.ek9",
  "name" : "total",
  "startLine" : 15
}

Response

{
  "conflicts" : [ {
    "kind" : "block-spans-control-flow",
    "message" : "The selection 15-18 does not align with whole statements - it starts above the body, whose statements begin on line 18. The statements it touches are lines 18-18: re-issue with startLine:18, endLine:18 to extract them.",
    "suggestedRange" : "18-18"
  } ],
  "edits" : [ ],
  "meta" : {
    "serverMs" : "<timing>"
  },
  "risk" : {
    "crossesModuleBoundary" : false,
    "estimatedSeverity" : "trivial",
    "fileCount" : 0,
    "lineCount" : 0,
    "touchesPublicApi" : false,
    "touchesTests" : false
  },
  "status" : "conflicts",
  "summary" : "Selection crosses a statement / control-flow boundary",
  "workspaceHash" : "<workspace-hash>",
  "wouldBeDiagnostics" : [ ],
  "written" : false
}

Block named by text

Request

{
  "file" : "task.ek9",
  "fromText" : "total <- 0.0",
  "name" : "sumOfLines",
  "toText" : "total += line"
}

Response

{
  "conflicts" : [ ],
  "edits" : [ {
    "after" : "        total <- sumOfLines()",
    "before" : "        total <- 0.0\n        for line in lines\n          total += line",
    "endLine" : 20,
    "file" : "task.ek9",
    "startLine" : 18,
    "type" : "replace"
  }, {
    "after" : "\n      sumOfLines()\n        <- rtn as Float?\n        total <- 0.0\n        for line in lines\n          total += line\n        rtn := total",
    "endLine" : 23,
    "file" : "task.ek9",
    "startLine" : 23,
    "type" : "insert"
  } ],
  "meta" : {
    "serverMs" : "<timing>"
  },
  "risk" : {
    "crossesModuleBoundary" : false,
    "estimatedSeverity" : "trivial",
    "fileCount" : 1,
    "lineCount" : 4,
...

Cut short here: 9 more lines. The whole recorded call.

See also