Skip to content

Commit 12d9fa5

Browse files
authored
Merge pull request #393 from codefori/fix/cleanup-test-stub-generation
Remove test stub code actions to migrate to test extension
2 parents d4dc61a + b8be716 commit 12d9fa5

File tree

2 files changed

+5
-298
lines changed

2 files changed

+5
-298
lines changed

extension/server/src/providers/codeActions.ts

Lines changed: 3 additions & 296 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,9 @@
1-
import { CodeAction, CodeActionKind, CodeActionParams, CreateFile, Position, Range, TextDocumentEdit, TextEdit, WorkspaceFolder } from 'vscode-languageserver';
1+
import { CodeAction, CodeActionKind, CodeActionParams, Position, Range, TextEdit } from 'vscode-languageserver';
22
import { TextDocument } from 'vscode-languageserver-textdocument';
3-
import { documents, parser, prettyKeywords } from '.';
4-
import Cache, { RpgleTypeDetail, RpgleVariableType } from '../../../../language/models/cache';
3+
import { documents, parser } from '.';
4+
import Cache from '../../../../language/models/cache';
55
import { getLinterCodeActions } from './linter/codeActions';
66
import { createExtract, caseInsensitiveReplaceAll } from './language';
7-
import { Keywords } from '../../../../language/parserTypes';
8-
import path = require('path');
9-
import { getWorkspaceFolder } from '../connection';
10-
import Declaration from '../../../../language/models/declaration';
11-
12-
interface TestCaseSpec {
13-
prototype: string[];
14-
testCase: string[];
15-
includes: string[];
16-
}
177

188
export default async function genericCodeActionsProvider(params: CodeActionParams): Promise<CodeAction[] | undefined> {
199
const uri = params.textDocument.uri;
@@ -43,11 +33,6 @@ export default async function genericCodeActionsProvider(params: CodeActionParam
4333
}
4434
}
4535

46-
const testActions = await getTestActions(document, docs, range);
47-
if (testActions) {
48-
actions.push(...testActions);
49-
}
50-
5136
const monitorAction = surroundWithMonitorAction(isFree, document, docs, range);
5237
if (monitorAction) {
5338
actions.push(monitorAction);
@@ -58,284 +43,6 @@ export default async function genericCodeActionsProvider(params: CodeActionParam
5843
return actions;
5944
}
6045

61-
export async function getTestActions(document: TextDocument, docs: Cache, range: Range): Promise<CodeAction[] | undefined> {
62-
const codeActions: CodeAction[] = [];
63-
64-
const exportProcedures = docs.procedures.filter(proc => proc.keyword[`EXPORT`]);
65-
if (exportProcedures.length > 0) {
66-
const workspaceFolder = await getWorkspaceFolder(document.uri); // TODO: Can workspace folder not be a requirement?
67-
if (workspaceFolder) {
68-
// Build new test file uri
69-
const parsedPath = path.parse(document.uri);
70-
const fileName = parsedPath.base;
71-
const testFileName = `${parsedPath.name}.test${parsedPath.ext}`;
72-
const testFileUri = workspaceFolder ?
73-
`${workspaceFolder.uri}/qtestsrc/${testFileName}` :
74-
`${parsedPath.dir}/${testFileName}`;
75-
76-
// Test case generation
77-
const currentProcedure = exportProcedures.find(sub => sub.range.start && sub.range.end && range.start.line >= sub.range.start && range.end.line <= sub.range.end);
78-
if (currentProcedure) {
79-
const testCaseSpec = await getTestCaseSpec(docs, currentProcedure, workspaceFolder);
80-
const newTestSuite = generateTestSuite([testCaseSpec]);
81-
const testCaseAction = CodeAction.create(`Generate test case for '${currentProcedure.name}'`, CodeActionKind.RefactorExtract);
82-
testCaseAction.edit = {
83-
documentChanges: [
84-
CreateFile.create(testFileUri, { ignoreIfExists: true }),
85-
TextDocumentEdit.create({ uri: testFileUri, version: null }, [TextEdit.insert(Position.create(0, 0), newTestSuite.join(`\n`))])
86-
]
87-
};
88-
codeActions.push(testCaseAction);
89-
}
90-
91-
// Test suite generation
92-
const newTestCases = await Promise.all(exportProcedures.map(async proc => await getTestCaseSpec(docs, proc, workspaceFolder)));
93-
const newTestSuite = generateTestSuite(newTestCases);
94-
const testSuiteAction = CodeAction.create(`Generate test suite for '${fileName}'`, CodeActionKind.RefactorExtract);
95-
testSuiteAction.edit = {
96-
documentChanges: [
97-
CreateFile.create(testFileUri, { ignoreIfExists: true }),
98-
TextDocumentEdit.create({ uri: testFileUri, version: null }, [TextEdit.insert(Position.create(0, 0), newTestSuite.join(`\n`))])
99-
]
100-
};
101-
codeActions.push(testSuiteAction);
102-
}
103-
}
104-
105-
return codeActions;
106-
}
107-
108-
109-
function generateTestSuite(testCaseSpecs: TestCaseSpec[]) {
110-
const prototypes = testCaseSpecs.map(tc => tc.prototype.length > 0 ? [``, ...tc.prototype] : tc.prototype).flat();
111-
const testCases = testCaseSpecs.map(tc => tc.testCase.length > 0 ? [``, ...tc.testCase] : tc.testCase).flat();
112-
const allIncludes = testCaseSpecs.map(tc => tc.includes).flat();
113-
const uniqueIncludes = [...new Set(allIncludes)];
114-
115-
return [
116-
`**free`,
117-
``,
118-
`ctl-opt nomain;`,
119-
...prototypes,
120-
``,
121-
`/include qinclude,TESTCASE`,
122-
...uniqueIncludes,
123-
...testCases
124-
]
125-
}
126-
127-
async function getTestCaseSpec(docs: Cache, procedure: Declaration, workspaceFolder: WorkspaceFolder): Promise<TestCaseSpec> {
128-
// Get procedure prototype
129-
const prototype = await getPrototype(procedure);
130-
131-
// Get inputs
132-
const inputDecs: string[] = [];
133-
const inputInits: string[] = [];
134-
const inputIncludes: string[] = [];
135-
for (const subItem of procedure.subItems) {
136-
const subItemType = docs.resolveType(subItem);
137-
138-
const subItemDec = getDeclaration(subItemType, `${subItem.name}`);
139-
inputDecs.push(...subItemDec);
140-
141-
const subItemInits = getInitializations(docs, subItemType, `${subItem.name}`);
142-
inputInits.push(...subItemInits);
143-
144-
const subItemIncludes = getIncludes(subItemType, workspaceFolder);
145-
inputIncludes.push(...subItemIncludes);
146-
}
147-
148-
// Get return
149-
const resolvedType = docs.resolveType(procedure);
150-
const actualDec = getDeclaration(resolvedType, 'actual');
151-
const expectedDec = getDeclaration(resolvedType, 'expected');
152-
const expectedInits = getInitializations(docs, resolvedType, 'expected');
153-
const returnIncludes = getIncludes(resolvedType, workspaceFolder);
154-
155-
// Get unique includes
156-
const includes = [...new Set([...inputIncludes, ...returnIncludes])];
157-
158-
// Get assertions
159-
const assertions = getAssertions(docs, resolvedType, 'expected', 'actual');
160-
161-
const testCase = [
162-
`dcl-proc test_${procedure.name} export;`,
163-
` dcl-pi *n extproc(*dclcase) end-pi;`,
164-
``,
165-
...inputDecs.map(dec => ` ${dec}`),
166-
...actualDec.map(dec => ` ${dec}`),
167-
...expectedDec.map(dec => ` ${dec}`),
168-
``,
169-
` // Input`,
170-
...inputInits.map(init => ` ${init}`),
171-
``,
172-
` // Actual results`,
173-
` actual = ${procedure.name}(${procedure.subItems.map(s => s.name).join(` : `)});`,
174-
``,
175-
` // Expected results`,
176-
...expectedInits.map(init => ` ${init}`),
177-
``,
178-
` // Assertions`,
179-
...assertions.map(assert => ` ${assert}`),
180-
`end-proc;`
181-
];
182-
183-
return {
184-
prototype,
185-
testCase,
186-
includes
187-
};
188-
}
189-
190-
function getDeclaration(detail: RpgleTypeDetail, name: string): string[] {
191-
const declarations: string[] = [];
192-
193-
if (detail) {
194-
if (detail.type) {
195-
declarations.push(`dcl-s ${name} ${detail.type.name}${detail.type.value ? `(${detail.type.value})` : ``};`);
196-
} else if (detail.reference) {
197-
declarations.push(`dcl-ds ${name} likeDs(${detail.reference.name});`);
198-
}
199-
}
200-
201-
return declarations;
202-
}
203-
204-
function getInitializations(docs: Cache, detail: RpgleTypeDetail, name: string): string[] {
205-
const inits: string[] = [];
206-
207-
if (detail) {
208-
if (detail.type) {
209-
const defaultValue = getDefaultValue(detail.type.name);
210-
inits.push(`${name} = ${defaultValue};`);
211-
} else if (detail.reference) {
212-
for (const subItem of detail.reference.subItems) {
213-
const subItemType = docs.resolveType(subItem);
214-
const subItemInits = subItemType ?
215-
getInitializations(docs, subItemType, `${name}.${subItem.name}`) : [];
216-
inits.push(...subItemInits);
217-
}
218-
}
219-
}
220-
221-
return inits;
222-
}
223-
224-
async function getPrototype(procedure: Declaration): Promise<string[]> {
225-
for (const reference of procedure.references) {
226-
const docs = await parser.getDocs(reference.uri);
227-
if (docs) {
228-
const prototype = docs.procedures.some(proc => proc.name === procedure.name && proc.keyword['EXTPROC'])
229-
if (prototype) {
230-
return [];
231-
}
232-
}
233-
}
234-
235-
return [
236-
`dcl-pr ${procedure.name} ${prettyKeywords(procedure.keyword, true)} extproc('${procedure.name.toLocaleUpperCase()}');`,
237-
...procedure.subItems.map(s => ` ${s.name} ${prettyKeywords(s.keyword, true)};`),
238-
`end-pr;`
239-
];
240-
}
241-
242-
function getIncludes(detail: RpgleTypeDetail, workspaceFolder: WorkspaceFolder): string[] {
243-
const includes: string[] = [];
244-
245-
if (detail.reference) {
246-
const structPath = detail.reference.position.path;
247-
if (workspaceFolder) {
248-
const relativePath = asPosix(path.relative(workspaceFolder.uri, structPath));
249-
if (!includes.includes(relativePath)) {
250-
includes.push(`/include '${relativePath}'`); // TODO: Support members style includes
251-
}
252-
}
253-
}
254-
255-
return includes;
256-
}
257-
258-
function getAssertions(docs: Cache, detail: RpgleTypeDetail, expected: string, actual: string): string[] {
259-
const assertions: string[] = [];
260-
261-
if (detail) {
262-
if (detail.type) {
263-
const assertion = getAssertion(detail.type.name);
264-
const fieldName = actual.split(`.`).pop();
265-
if (assertion === `assert`) {
266-
assertions.push(`${assertion}(${expected} = ${actual}${fieldName ? ` : '${fieldName}'` : ``});`);
267-
} else {
268-
assertions.push(`${assertion}(${expected} : ${actual}${fieldName ? ` : '${fieldName}'` : ``});`);
269-
}
270-
} else if (detail.reference) {
271-
for (const subItem of detail.reference.subItems) {
272-
const subItemType = docs.resolveType(subItem);
273-
const subItemAssertions = subItemType ?
274-
getAssertions(docs, subItemType, `${expected}.${subItem.name}`, `${actual}.${subItem.name}`) : [];
275-
assertions.push(...subItemAssertions);
276-
}
277-
}
278-
}
279-
280-
return assertions;
281-
}
282-
283-
function getDefaultValue(type: RpgleVariableType): string {
284-
switch (type) {
285-
case `char`:
286-
case `varchar`:
287-
return `''`;
288-
case `int`:
289-
case `uns`:
290-
return `0`;
291-
case `packed`:
292-
case `zoned`:
293-
return `0.0`;
294-
case `ind`:
295-
return `*off`;
296-
case `date`:
297-
return `%date('0001-01-01' : *iso)`;
298-
case `time`:
299-
return `%time('00.00.00' : *iso)`;
300-
case `timestamp`:
301-
return `%timestamp('0001-01-01-00.00.00.000000' : *iso)`;
302-
case `pointer`:
303-
return `*null`;
304-
default:
305-
return 'unknown';
306-
}
307-
}
308-
309-
function getAssertion(type: RpgleVariableType): string {
310-
switch (type) {
311-
case `char`:
312-
case `varchar`:
313-
return `aEqual`;
314-
case `int`:
315-
case `uns`:
316-
return `iEqual`;
317-
case `packed`:
318-
case `zoned`:
319-
return `assert`;
320-
case `ind`:
321-
return `nEqual`;
322-
case `date`:
323-
return `assert`;
324-
case `time`:
325-
return `assert`;
326-
case `timestamp`:
327-
return `assert`;
328-
case `pointer`:
329-
return `assert`;
330-
default:
331-
return 'unknown';
332-
}
333-
}
334-
335-
function asPosix(inPath?: string) {
336-
return inPath ? inPath.split(path.sep).join(path.posix.sep) : ``;
337-
}
338-
33946
function lineAt(document: TextDocument, line: number): string {
34047
return document.getText(Range.create(line, 0, line, 1000)).trimEnd();
34148
}

language/models/cache.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ const newInds = () => {
2222
})
2323
};
2424

25-
export type RpgleVariableType = `char` | `varchar` | `int` | `uns` | `packed` | `zoned` | `ind` | `date` | `time` | `timestamp` | `pointer` | `float` | `graph`;
26-
const validTypes: RpgleVariableType[] = [`char`, `varchar`, `int`, `uns`, `packed`, `zoned`, `ind`, `date`, `time`, `timestamp`, `pointer`, `float`, `graph`];
25+
export type RpgleVariableType = `char` | `varchar` | `ucs2` | `varucs2` | `int` | `uns` | `packed` | `zoned` | `float` | `ind` | `date` | `time` | `timestamp` | `pointer` | `graph` | `vargraph`;
26+
const validTypes: RpgleVariableType[] = [`char`, `varchar`, `ucs2`, `varucs2`, `int`, `uns`, `packed`, `zoned`, `float`, `ind`, `date`, `time`, `timestamp`, `pointer`, `graph`, `vargraph`];
2727

2828
export interface RpgleTypeDetail {
2929
type?: { name: RpgleVariableType, value?: string };

0 commit comments

Comments
 (0)