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' ;
22import { 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' ;
55import { getLinterCodeActions } from './linter/codeActions' ;
66import { 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
188export 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-
33946function lineAt ( document : TextDocument , line : number ) : string {
34047 return document . getText ( Range . create ( line , 0 , line , 1000 ) ) . trimEnd ( ) ;
34148}
0 commit comments