Skip to content

Commit a91e607

Browse files
authored
Merge pull request #374 from codefori/feature/generate_test
Code Actions cleanup
2 parents 67c9183 + fcfd030 commit a91e607

File tree

7 files changed

+380
-195
lines changed

7 files changed

+380
-195
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import { CodeAction, CodeActionKind, CodeActionParams, Position, Range, TextEdit } from 'vscode-languageserver';
2+
import { TextDocument } from 'vscode-languageserver-textdocument';
3+
import { documents, parser, prettyKeywords } from '.';
4+
import Cache from '../../../../language/models/cache';
5+
import { getLinterCodeActions } from './linter/codeActions';
6+
import { createExtract, caseInsensitiveReplaceAll } from './language';
7+
8+
export default async function genericCodeActionsProvider(params: CodeActionParams): Promise<CodeAction[] | undefined> {
9+
const uri = params.textDocument.uri;
10+
const range = params.range;
11+
const document = documents.get(uri);
12+
13+
let actions: CodeAction[] = [];
14+
15+
if (document) {
16+
const docs = await parser.getDocs(document.uri);
17+
if (docs) {
18+
const isFree = (document.getText(Range.create(0, 0, 0, 6)).toUpperCase() === `**FREE`);
19+
if (isFree) {
20+
const subroutineOption = getSubroutineActions(document, docs, range);
21+
if (subroutineOption) {
22+
return [subroutineOption];
23+
}
24+
25+
const extractOption = getExtractProcedureAction(document, docs, range);
26+
if (extractOption) {
27+
return [extractOption];
28+
}
29+
30+
const linterActions = await getLinterCodeActions(docs, document, range);
31+
if (linterActions) {
32+
actions = actions.concat(linterActions);
33+
}
34+
}
35+
36+
// const testCaseOption = getTestCaseAction(document, docs, range);
37+
// if (testCaseOption) {
38+
// actions.push(testCaseOption);
39+
// }
40+
}
41+
}
42+
43+
return actions;
44+
}
45+
46+
export function getTestCaseAction(document: TextDocument, docs: Cache, range: Range): CodeAction | undefined {
47+
const currentProcedure = docs.procedures.find(sub => range.start.line >= sub.position.range.line && sub.range.start && sub.range.end);
48+
if (currentProcedure) {
49+
50+
const refactorAction = CodeAction.create(`Create IBM i test case`, CodeActionKind.RefactorExtract);
51+
52+
refactorAction.edit = {
53+
changes: {
54+
['mynewtest.rpgle']: [
55+
TextEdit.insert(
56+
Position.create(0, 0), // Insert at the start of the new test case file
57+
[
58+
`**free`,
59+
``,
60+
`dcl-proc test_${currentProcedure.name.toLowerCase()} export;`,
61+
``,
62+
`end-proc;`
63+
].join(`\n`)
64+
65+
)
66+
]
67+
},
68+
};
69+
70+
return refactorAction;
71+
}
72+
}
73+
74+
export function getSubroutineActions(document: TextDocument, docs: Cache, range: Range): CodeAction|undefined {
75+
if (range.start.line === range.end.line) {
76+
const currentGlobalSubroutine = docs.subroutines.find(sub => sub.position.range.line === range.start.line && sub.range.start && sub.range.end);
77+
78+
if (currentGlobalSubroutine) {
79+
const subroutineRange = Range.create(
80+
Position.create(currentGlobalSubroutine.range.start!, 0),
81+
Position.create(currentGlobalSubroutine.range.end!, 1000)
82+
);
83+
84+
const bodyRange = Range.create(
85+
Position.create(currentGlobalSubroutine.range.start! + 1, 0),
86+
Position.create(currentGlobalSubroutine.range.end! - 1, 0)
87+
);
88+
89+
// First, let's create the extract data
90+
const extracted = createExtract(document, bodyRange, docs);
91+
92+
// Create the new procedure body
93+
const newProcedure = [
94+
`Dcl-Proc ${currentGlobalSubroutine.name};`,
95+
` Dcl-Pi *N;`,
96+
...extracted.references.map((ref, i) => ` ${extracted.newParamNames[i]} ${ref.dec.type === `struct` ? `LikeDS` : `Like`}(${ref.dec.name});`),
97+
` End-Pi;`,
98+
``,
99+
caseInsensitiveReplaceAll(extracted.newBody, `leavesr`, `return`),
100+
`End-Proc;`
101+
].join(`\n`)
102+
103+
// Then update the references that invokes this subroutine
104+
const referenceUpdates: TextEdit[] = currentGlobalSubroutine.references.map(ref => {
105+
const lineNumber = document.positionAt(ref.offset.start).line;
106+
// If this reference is outside of the subroutine
107+
if (lineNumber < currentGlobalSubroutine.range.start! || lineNumber > currentGlobalSubroutine.range.end!) {
108+
return TextEdit.replace(
109+
Range.create(
110+
// - 5 `EXSR `
111+
document.positionAt(ref.offset.start - 5),
112+
document.positionAt(ref.offset.end)
113+
),
114+
currentGlobalSubroutine.name + `(${extracted.references.map(r => r.dec.name).join(`:`)})`
115+
);
116+
}
117+
}).map(x => x) as TextEdit[];
118+
119+
const refactorAction = CodeAction.create(`Convert to procedure`, CodeActionKind.RefactorExtract);
120+
refactorAction.edit = {
121+
changes: {
122+
[document.uri]: [
123+
...referenceUpdates,
124+
TextEdit.replace(subroutineRange, newProcedure)
125+
]
126+
},
127+
};
128+
129+
return refactorAction;
130+
}
131+
}
132+
}
133+
134+
export function getExtractProcedureAction(document: TextDocument, docs: Cache, range: Range): CodeAction|undefined {
135+
if (range.end.line > range.start.line) {
136+
const lastLine = document.offsetAt({line: document.lineCount, character: 0});
137+
138+
const extracted = createExtract(document, range, docs);
139+
140+
const newProcedure = [
141+
`Dcl-Proc NewProcedure;`,
142+
` Dcl-Pi *N;`,
143+
...extracted.references.map((ref, i) => ` ${extracted.newParamNames[i]} ${ref.dec.type === `struct` ? `LikeDS` : `Like`}(${ref.dec.name});`),
144+
` End-Pi;`,
145+
``,
146+
extracted.newBody,
147+
`End-Proc;`
148+
].join(`\n`)
149+
150+
const newAction = CodeAction.create(`Extract to new procedure`, CodeActionKind.RefactorExtract);
151+
152+
// First do the exit
153+
newAction.edit = {
154+
changes: {
155+
[document.uri]: [
156+
TextEdit.replace(extracted.range, `NewProcedure(${extracted.references.map(r => r.dec.name).join(`:`)});`),
157+
TextEdit.insert(document.positionAt(lastLine), `\n\n`+newProcedure)
158+
]
159+
},
160+
};
161+
162+
// Then format the document
163+
newAction.command = {
164+
command: `editor.action.formatDocument`,
165+
title: `Format`
166+
};
167+
168+
return newAction;
169+
}
170+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { TextDocument } from 'vscode-languageserver-textdocument';
2+
import { Range } from "vscode-languageserver";
3+
import Cache from "../../../../language/models/cache";
4+
5+
export function caseInsensitiveReplaceAll(text: string, search: string, replace: string) {
6+
return text.replace(new RegExp(search, `gi`), replace);
7+
}
8+
9+
export function createExtract(document: TextDocument, userRange: Range, docs: Cache) {
10+
const range = Range.create(userRange.start.line, 0, userRange.end.line, 1000);
11+
const references = docs.referencesInRange(document.uri, {start: document.offsetAt(range.start), end: document.offsetAt(range.end)});
12+
const validRefs = references.filter(ref => [`struct`, `subitem`, `variable`].includes(ref.dec.type));
13+
14+
const nameDiffSize = 1; // Always once since we only add 'p' at the start
15+
const newParamNames = validRefs.map(ref => `p${ref.dec.name}`);
16+
let newBody = document.getText(range);
17+
18+
const rangeStartOffset = document.offsetAt(range.start);
19+
20+
// Fix the found offset lengths to be relative to the new procedure
21+
for (let i = validRefs.length - 1; i >= 0; i--) {
22+
for (let y = validRefs[i].refs.length - 1; y >= 0; y--) {
23+
validRefs[i].refs[y] = {
24+
start: validRefs[i].refs[y].start - rangeStartOffset,
25+
end: validRefs[i].refs[y].end - rangeStartOffset
26+
};
27+
}
28+
}
29+
30+
// Then let's fix the references to use the new names
31+
for (let i = validRefs.length - 1; i >= 0; i--) {
32+
for (let y = validRefs[i].refs.length - 1; y >= 0; y--) {
33+
const ref = validRefs[i].refs[y];
34+
35+
newBody = newBody.slice(0, ref.start) + newParamNames[i] + newBody.slice(ref.end);
36+
ref.end += nameDiffSize;
37+
38+
// Then we need to update the offset of the next references
39+
for (let z = i - 1; z >= 0; z--) {
40+
for (let x = validRefs[z].refs.length - 1; x >= 0; x--) {
41+
if (validRefs[z].refs[x].start > ref.end) {
42+
validRefs[z].refs[x] = {
43+
start: validRefs[z].refs[x].start + nameDiffSize,
44+
end: validRefs[z].refs[x].end + nameDiffSize
45+
};
46+
}
47+
}
48+
}
49+
}
50+
}
51+
52+
return {
53+
newBody,
54+
newParamNames,
55+
references: validRefs,
56+
range
57+
}
58+
}
Lines changed: 14 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,22 @@
11
import { CodeAction, CodeActionKind, CodeActionParams, Range } from 'vscode-languageserver';
22
import { getActions, getExtractProcedureAction, getSubroutineActions, refreshLinterDiagnostics } from '.';
33
import { documents, parser } from '..';
4+
import { TextDocument } from 'vscode-languageserver-textdocument';
5+
import Cache from '../../../../../language/models/cache';
46

5-
export default async function codeActionsProvider(params: CodeActionParams): Promise<CodeAction[]|undefined> {
6-
const uri = params.textDocument.uri;
7-
const range = params.range;
8-
const document = documents.get(uri);
7+
/**
8+
* Get the CodeActions for a given document and range.
9+
*/
10+
export async function getLinterCodeActions(docs: Cache, document: TextDocument, range: Range) {
11+
const detail = await refreshLinterDiagnostics(document, docs, false);
12+
if (detail) {
13+
const fixErrors = detail.errors.filter(error =>
14+
range.start.line >= document.positionAt(error.offset.start!).line &&
15+
range.end.line <= document.positionAt(error.offset.end!).line
16+
);
917

10-
if (document) {
11-
const isFree = (document.getText(Range.create(0, 0, 0, 6)).toUpperCase() === `**FREE`);
12-
if (isFree) {
13-
const docs = await parser.getDocs(document.uri);
14-
15-
if (docs) {
16-
const subroutineOption = getSubroutineActions(document, docs, range);
17-
if (subroutineOption) {
18-
return [subroutineOption];
19-
}
20-
21-
const extractOption = getExtractProcedureAction(document, docs, range);
22-
if (extractOption) {
23-
return [extractOption];
24-
}
25-
26-
const detail = await refreshLinterDiagnostics(document, docs, false);
27-
if (detail) {
28-
const fixErrors = detail.errors.filter(error =>
29-
range.start.line >= document.positionAt(error.offset.start!).line &&
30-
range.end.line <= document.positionAt(error.offset.end!).line
31-
);
32-
33-
if (fixErrors.length > 0) {
34-
return getActions(document, fixErrors);
35-
}
36-
}
37-
}
18+
if (fixErrors.length > 0) {
19+
return getActions(document, fixErrors);
3820
}
3921
}
40-
41-
return;
4222
}

0 commit comments

Comments
 (0)