Skip to content

Commit

Permalink
Merge branch 'main' into add_lowercase_directives
Browse files Browse the repository at this point in the history
  • Loading branch information
richardm90 committed Feb 4, 2024
2 parents 7ed6c66 + a044491 commit e2cc17e
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 63 deletions.
14 changes: 7 additions & 7 deletions extension/client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion extension/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"vscode-languageclient": "^7.0.0"
},
"devDependencies": {
"@halcyontech/vscode-ibmi-types": "^2.1.0",
"@halcyontech/vscode-ibmi-types": "^2.6.5",
"@types/vscode": "^1.63.0",
"@vscode/test-electron": "^2.1.2"
}
Expand Down
33 changes: 23 additions & 10 deletions extension/client/src/linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export function initialise(context: ExtensionContext) {
const instance = getInstance();
const editor = window.activeTextEditor;

let exists = false;

if (editor && ![`member`, `streamfile`].includes(editor.document.uri.scheme)) {
const workspaces = workspace.workspaceFolders;
if (workspaces && workspaces.length > 0) {
Expand Down Expand Up @@ -44,15 +46,23 @@ export function initialise(context: ExtensionContext) {
}

} else if (instance && instance.getConnection()) {
const connection = instance.getConnection();
const content = instance.getContent();

/** @type {"member"|"streamfile"} */
let type = `member`;
let configPath: string | undefined;

if (filter && filter.description) {
// Bad way to get the library for the filter ..
const library = filter.description.split(`/`)[0];
const library = (filter.description.split(`/`)[0]).toLocaleUpperCase();
configPath = `${library}/VSCODE/RPGLINT.JSON`;

exists = (await connection.runCommand({
command: `CHKOBJ OBJ(${library}/VSCODE) OBJTYPE(*FILE) MBR(RPGLINT)`,
noLibList: true
})).code === 0;

} else if (editor) {
//@ts-ignore
type = editor.document.uri.scheme;
Expand All @@ -74,12 +84,18 @@ export function initialise(context: ExtensionContext) {
});

configPath = memberUri.path;

exists = (await connection.runCommand({
command: `CHKOBJ OBJ(${memberPath.library!.toLocaleUpperCase()}/VSCODE) OBJTYPE(*FILE) MBR(RPGLINT)`,
noLibList: true
})).code === 0;
break;

case `streamfile`:
const config = instance.getConfig();
if (config.homeDirectory) {
configPath = path.posix.join(config.homeDirectory, `.vscode`, `rpglint.json`)
exists = await content.testStreamFile(configPath, `r`);
}
break;
}
Expand All @@ -90,12 +106,9 @@ export function initialise(context: ExtensionContext) {
if (configPath) {
console.log(`Current path: ${configPath}`);

const exists = await commands.executeCommand(`code-for-ibmi.openEditable`, configPath, 1);

if (!exists) {
const connection = instance.getConnection();
const content = instance.getContent();

if (exists) {
await commands.executeCommand(`code-for-ibmi.openEditable`, configPath);
} else {
window.showErrorMessage(`RPGLE linter config doesn't exist for this file. Would you like to create a default at ${configPath}?`, `Yes`, `No`).then
(async (value) => {
if (value === `Yes`) {
Expand Down Expand Up @@ -157,10 +170,10 @@ export function initialise(context: ExtensionContext) {
)
}

function parseMemberUri(path: string): {asp?: string, library?: string, file?: string, name: string} {
const parts = path.split(`/`).map(s => s.split(`,`)).flat().filter(s => s.length >= 1);
function parseMemberUri(fullPath: string): {asp?: string, library?: string, file?: string, name: string} {
const parts = fullPath.split(`/`).map(s => s.split(`,`)).flat().filter(s => s.length >= 1);
return {
name: parts[parts.length - 1],
name: path.parse(parts[parts.length - 1]).name,
file: parts[parts.length - 2],
library: parts[parts.length - 3],
asp: parts[parts.length - 4]
Expand Down
14 changes: 12 additions & 2 deletions extension/server/src/providers/linter/codeActions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CodeAction, CodeActionParams, Range } from 'vscode-languageserver';
import { getActions, refreshLinterDiagnostics } from '.';
import { CodeAction, CodeActionKind, CodeActionParams, Range } from 'vscode-languageserver';
import { getActions, getExtractProcedureAction, getSubroutineActions, refreshLinterDiagnostics } from '.';
import { documents, parser } from '..';

export default async function codeActionsProvider(params: CodeActionParams): Promise<CodeAction[]|undefined> {
Expand All @@ -13,6 +13,16 @@ export default async function codeActionsProvider(params: CodeActionParams): Pro
const docs = await parser.getDocs(document.uri);

if (docs) {
const subroutineOption = getSubroutineActions(document, docs, range);
if (subroutineOption) {
return [subroutineOption];
}

const extractOption = getExtractProcedureAction(document, docs, range);
if (extractOption) {
return [extractOption];
}

const detail = await refreshLinterDiagnostics(document, docs, false);
if (detail) {
const fixErrors = detail.errors.filter(error =>
Expand Down
156 changes: 155 additions & 1 deletion extension/server/src/providers/linter/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path = require('path');
import { CodeAction, CodeActionKind, Diagnostic, DiagnosticSeverity, DidChangeWatchedFilesParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, Range, TextDocumentChangeEvent, TextEdit, WorkspaceEdit, _Connection } from 'vscode-languageserver';
import { CodeAction, CodeActionKind, Diagnostic, DiagnosticSeverity, DidChangeWatchedFilesParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, Position, Range, TextDocumentChangeEvent, TextEdit, WorkspaceEdit, _Connection } from 'vscode-languageserver';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { URI } from 'vscode-uri';
import { documents, parser } from '..';
Expand Down Expand Up @@ -432,4 +432,158 @@ export function getActions(document: TextDocument, errors: IssueRange[]) {
});

return actions;
}

export function getSubroutineActions(document: TextDocument, docs: Cache, range: Range): CodeAction|undefined {
if (range.start.line === range.end.line) {
const currentGlobalSubroutine = docs.subroutines.find(sub => sub.position.line === range.start.line);

if (currentGlobalSubroutine) {
const subroutineRange = Range.create(
Position.create(currentGlobalSubroutine.range.start, 0),
Position.create(currentGlobalSubroutine.range.end, 1000)
);

const bodyRange = Range.create(
Position.create(currentGlobalSubroutine.range.start + 1, 0),
Position.create(currentGlobalSubroutine.range.end - 1, 0)
);

// First, let's create the extract data
const extracted = createExtract(document, bodyRange, docs);

// Create the new procedure body
const newProcedure = [
`Dcl-Proc ${currentGlobalSubroutine.name};`,
` Dcl-Pi *N;`,
...extracted.references.map((ref, i) => ` ${extracted.newParamNames[i]} ${ref.dec.type === `struct` ? `LikeDS` : `Like`}(${ref.dec.name});`),
` End-Pi;`,
``,
caseInsensitiveReplaceAll(extracted.newBody, `leavesr`, `return`),
`End-Proc;`
].join(`\n`)

// Then update the references that invokes this subroutine
const referenceUpdates: TextEdit[] = currentGlobalSubroutine.references.map(ref => {
const lineNumber = document.positionAt(ref.offset.position).line;
// If this reference is outside of the subroutine
if (lineNumber < currentGlobalSubroutine.range.start || lineNumber > currentGlobalSubroutine.range.end) {
return TextEdit.replace(
Range.create(
// - 5 `EXSR `
document.positionAt(ref.offset.position - 5),
document.positionAt(ref.offset.end)
),
currentGlobalSubroutine.name + `(${extracted.references.map(r => r.dec.name).join(`:`)})`
);
}
}).map(x => x) as TextEdit[];

const refactorAction = CodeAction.create(`Convert to procedure`, CodeActionKind.RefactorExtract);
refactorAction.edit = {
changes: {
[document.uri]: [
...referenceUpdates,
TextEdit.replace(subroutineRange, newProcedure)
]
},
};

return refactorAction;
}
}
}

export function getExtractProcedureAction(document: TextDocument, docs: Cache, range: Range): CodeAction|undefined {
if (range.end.line > range.start.line) {
const lastLine = document.offsetAt({line: document.lineCount, character: 0});

const extracted = createExtract(document, range, docs);

const newProcedure = [
`Dcl-Proc NewProcedure;`,
` Dcl-Pi *N;`,
...extracted.references.map((ref, i) => ` ${extracted.newParamNames[i]} ${ref.dec.type === `struct` ? `LikeDS` : `Like`}(${ref.dec.name});`),
` End-Pi;`,
``,
extracted.newBody,
`End-Proc;`
].join(`\n`)

const newAction = CodeAction.create(`Extract to new procedure`, CodeActionKind.RefactorExtract);

// First do the exit
newAction.edit = {
changes: {
[document.uri]: [
TextEdit.replace(extracted.range, `NewProcedure(${extracted.references.map(r => r.dec.name).join(`:`)});`),
TextEdit.insert(document.positionAt(lastLine), `\n\n`+newProcedure)
]
},
};

// Then format the document
newAction.command = {
command: `editor.action.formatDocument`,
title: `Format`
};

return newAction;
}
}

function caseInsensitiveReplaceAll(text: string, search: string, replace: string) {
return text.replace(new RegExp(search, `gi`), replace);
}


function createExtract(document: TextDocument, userRange: Range, docs: Cache) {
const range = Range.create(userRange.start.line, 0, userRange.end.line, 1000);
const references = docs.referencesInRange({position: document.offsetAt(range.start), end: document.offsetAt(range.end)});
const validRefs = references.filter(ref => [`struct`, `subitem`, `variable`].includes(ref.dec.type));

const nameDiffSize = 1; // Always once since we only add 'p' at the start
const newParamNames = validRefs.map(ref => `p${ref.dec.name}`);
let newBody = document.getText(range);

const rangeStartOffset = document.offsetAt(range.start);

// Fix the found offset lengths to be relative to the new procedure
for (let i = validRefs.length - 1; i >= 0; i--) {
for (let y = validRefs[i].refs.length - 1; y >= 0; y--) {
validRefs[i].refs[y] = {
position: validRefs[i].refs[y].position - rangeStartOffset,
end: validRefs[i].refs[y].end - rangeStartOffset
};
}
}

// Then let's fix the references to use the new names
for (let i = validRefs.length - 1; i >= 0; i--) {
for (let y = validRefs[i].refs.length - 1; y >= 0; y--) {
const ref = validRefs[i].refs[y];

newBody = newBody.slice(0, ref.position) + newParamNames[i] + newBody.slice(ref.end);
ref.end += nameDiffSize;

// Then we need to update the offset of the next references
for (let z = i - 1; z >= 0; z--) {
for (let x = validRefs[z].refs.length - 1; x >= 0; x--) {
if (validRefs[z].refs[x].position > ref.end) {
validRefs[z].refs[x] = {
position: validRefs[z].refs[x].position + nameDiffSize,
end: validRefs[z].refs[x].end + nameDiffSize
};
}
}
}
}
}

return {
newBody,
newParamNames,
references: validRefs,
range
}
}
2 changes: 1 addition & 1 deletion extension/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ connection.onInitialize((params: InitializeParams) => {
result.capabilities.documentSymbolProvider = true;
result.capabilities.definitionProvider = true;
result.capabilities.completionProvider = {
triggerCharacters: [` `, `.`, `:`]
triggerCharacters: [`.`, `:`]
};
result.capabilities.hoverProvider = true;
result.capabilities.referencesProvider = true;
Expand Down
12 changes: 4 additions & 8 deletions language/linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,8 +387,7 @@ export default class Linter {
if (rules.NoGlobalSubroutines) {
errors.push({
offset: { position: statement[0].range.start, end: statement[0].range.end },
type: `NoGlobalSubroutines`,
newValue: `Dcl-Proc`
type: `NoGlobalSubroutines`
});
}
}
Expand Down Expand Up @@ -584,8 +583,7 @@ export default class Linter {
if (rules.NoGlobalSubroutines) {
errors.push({
offset: { position: statement[0].range.start, end: statement[0].range.end },
type: `NoGlobalSubroutines`,
newValue: `End-Proc`
type: `NoGlobalSubroutines`
});
}
}
Expand Down Expand Up @@ -661,8 +659,7 @@ export default class Linter {
if (rules.NoGlobalSubroutines && !inProcedure) {
errors.push({
type: `NoGlobalSubroutines`,
offset: { position: statement[0].range.start, end: statement[statement.length - 1].range.end },
newValue: `return`
offset: { position: statement[0].range.start, end: statement[statement.length - 1].range.end }
});
}
break;
Expand All @@ -672,8 +669,7 @@ export default class Linter {
if (globalScope.subroutines.find(sub => sub.name.toUpperCase() === statement[1].value.toUpperCase())) {
errors.push({
type: `NoGlobalSubroutines`,
offset: { position: statement[0].range.start, end: statement[statement.length - 1].range.end },
newValue: `${statement[1].value}()`
offset: { position: statement[0].range.start, end: statement[statement.length - 1].range.end }
});
}
}
Expand Down
Loading

0 comments on commit e2cc17e

Please sign in to comment.