Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -1928,6 +1928,7 @@ export interface ProjectInfo {
name?: string;
title?: string;
orgName?: string;
org?: string;
version?: string;
projectPath?: string;
children?: ProjectInfo[];
Expand Down
28 changes: 23 additions & 5 deletions workspaces/ballerina/ballerina-extension/src/stateMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
fetchScope,
getOrgPackageName,
UndoRedoManager,
getPackageName
getOrgAndPackageName
} from './utils';
import { buildProjectsStructure } from './utils/project-artifacts';
import { runCommandWithOutput } from './utils/runCommand';
Expand Down Expand Up @@ -61,6 +61,15 @@ const stateMachine = createMachine<MachineContext>(
on: {
RESET_TO_EXTENSION_READY: {
target: "extensionReady",
actions: assign({
documentUri: undefined,
position: undefined,
identifier: undefined,
projectPath: undefined,
scope: undefined,
org: undefined,
package: undefined
})
},
UPDATE_PROJECT_STRUCTURE: {
actions: [
Expand Down Expand Up @@ -239,8 +248,11 @@ const stateMachine = createMachine<MachineContext>(
OPEN_VIEW: {
target: "viewActive",
actions: assign({
org: (context, event) => event.viewLocation?.org,
package: (context, event) => event.viewLocation?.package,
view: (context, event) => event.viewLocation.view,
documentUri: (context, event) => event.viewLocation.documentUri,
projectPath: (context, event) => event.viewLocation?.projectPath,
position: (context, event) => event.viewLocation.position,
identifier: (context, event) => event.viewLocation.identifier,
serviceType: (context, event) => event.viewLocation.serviceType,
Expand Down Expand Up @@ -319,6 +331,7 @@ const stateMachine = createMachine<MachineContext>(
identifier: (context, event) => event.viewLocation.identifier,
serviceType: (context, event) => event.viewLocation.serviceType,
projectPath: (context, event) => event.viewLocation?.projectPath || context?.projectPath,
org: (context, event) => event.viewLocation?.org || context?.org,
package: (context, event) => event.viewLocation?.package || context?.package,
type: (context, event) => event.viewLocation?.type,
isGraphql: (context, event) => event.viewLocation?.isGraphql,
Expand Down Expand Up @@ -515,7 +528,7 @@ const stateMachine = createMachine<MachineContext>(
},
findView(context, event): Promise<void> {
return new Promise(async (resolve, reject) => {
const packageName = getPackageName(context.projectInfo, context.projectPath);
const { orgName, packageName } = getOrgAndPackageName(context.projectInfo, context.projectPath);
if (!context.view && context.langClient) {
if (!context.position || ("groupId" in context.position)) {
if (!context.projectPath && context.workspacePath) {
Expand All @@ -529,7 +542,8 @@ const stateMachine = createMachine<MachineContext>(
location: {
view: MACHINE_VIEW.PackageOverview,
documentUri: context.documentUri,
package: packageName || context.package
org: orgName || context.org,
package: packageName || context.package,
}
});
}
Expand All @@ -547,6 +561,7 @@ const stateMachine = createMachine<MachineContext>(
documentUri: context.documentUri,
position: context.position,
identifier: context.identifier,
org: orgName || context.org,
package: packageName || context.package,
type: context?.type,
isGraphql: context?.isGraphql,
Expand Down Expand Up @@ -598,8 +613,8 @@ const stateMachine = createMachine<MachineContext>(
if (!selectedEntry?.location.view) {
return resolve(
context.workspacePath
? { view: MACHINE_VIEW.WorkspaceOverview }
: { view: MACHINE_VIEW.PackageOverview, documentUri: context.documentUri }
? { view: MACHINE_VIEW.WorkspaceOverview }
: { view: MACHINE_VIEW.PackageOverview, documentUri: context.documentUri }
);
}

Expand Down Expand Up @@ -726,6 +741,9 @@ export function openView(type: EVENT_TYPE, viewLocation: VisualizerLocation, res
}
extension.hasPullModuleResolved = false;
extension.hasPullModuleNotification = false;
const { orgName, packageName } = getOrgAndPackageName(StateMachine.context().projectInfo, viewLocation.projectPath);
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential issue: The getOrgAndPackageName function is called with viewLocation.projectPath which might be undefined. While the function handles this by returning empty strings, this could lead to incorrect org/package information being set in the view location. Consider validating that viewLocation.projectPath exists before calling this function, or provide a fallback to StateMachine.context().projectPath.

Suggested change
const { orgName, packageName } = getOrgAndPackageName(StateMachine.context().projectInfo, viewLocation.projectPath);
const { orgName, packageName } = getOrgAndPackageName(
StateMachine.context().projectInfo,
viewLocation.projectPath || StateMachine.context().projectPath
);

Copilot uses AI. Check for mistakes.
viewLocation.org = orgName;
viewLocation.package = packageName;
stateService.send({ type: type, viewLocation: viewLocation });
}

Expand Down
14 changes: 7 additions & 7 deletions workspaces/ballerina/ballerina-extension/src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,12 @@ function compareVersions(
if (current.major !== minimum.major) {
return current.major > minimum.major;
}

// Major versions are equal, compare minor
if (current.minor !== minimum.minor) {
return current.minor > minimum.minor;
}

// Major and minor are equal, compare patch
return current.patch >= minimum.patch;
}
Expand Down Expand Up @@ -357,20 +357,20 @@ export function setupBIFiles(projectDir: string): void {
});
}

export function getPackageName(projectInfo: ProjectInfo, projectPath: string): string {
export function getOrgAndPackageName(projectInfo: ProjectInfo, projectPath: string): { orgName: string, packageName: string } {
if (!projectPath || !projectInfo) {
return "";
return { orgName: '', packageName: '' };
}

if (projectInfo.children?.length) {
const matchedProject = projectInfo.children.find(
(child) => child.projectPath === projectPath
);

if (matchedProject) {
return matchedProject.title || matchedProject.name;
return { orgName: matchedProject.org || matchedProject.orgName, packageName: matchedProject.name };
}
}

return projectInfo.title || projectInfo.name;
return { orgName: projectInfo.org || projectInfo.orgName, packageName: projectInfo.name || projectInfo.title };
}
Original file line number Diff line number Diff line change
Expand Up @@ -499,11 +499,17 @@ export function ConfigObjectEditor(props: ObjectEditorProps) {
// If there's an existing config value, parse it and merge with recordConfig
if (configValue) {
try {
let parsedValue;
let parsedValue: any;
if (typeof configValue === 'string') {
// Attempt to fix JSON if it is JavaScript-style (unquoted keys)
const fixedJson = configValue.replace(/([{,]\s*)([a-zA-Z0-9_]+)\s*:/g, '$1"$2":');
parsedValue = JSON.parse(fixedJson);
// Check if there are string values that are not wrapped in quotes
Object.keys(parsedValue).forEach(key => {
if (typeof parsedValue[key] === 'string' && !/^".*"$/.test(parsedValue[key])) {
parsedValue[key] = `"${parsedValue[key]}"`;
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as in ConfigurableItem/index.tsx: The string wrapping logic doesn't escape internal quotes. When wrapping string values that contain quotes, this will produce invalid JSON. Consider escaping quotes before wrapping:

if (typeof parsedValue[key] === 'string' && !/^".*"$/.test(parsedValue[key])) {
    parsedValue[key] = `"${parsedValue[key].replace(/"/g, '\\"')}"`;
}
Suggested change
parsedValue[key] = `"${parsedValue[key]}"`;
parsedValue[key] = JSON.stringify(parsedValue[key]);

Copilot uses AI. Check for mistakes.
}
});
} else {
parsedValue = configValue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,20 @@ export function ConfigurableItem(props: ConfigurableItemProps) {
setEditConfigVariableFormOpen(true);
};

const handleTextAreaChange = (value: any) => {
if (configVariable.properties?.type?.value === 'string' && !/^".*"$/.test(value)) {
value = `"${value}"`;
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string wrapping logic doesn't handle strings that already contain internal quotes or need escaping. For example:

  • he said "hello""he said "hello"" (produces invalid JSON/string literal)
  • Values with newlines or special characters may not be properly escaped

Consider escaping internal quotes before wrapping, or validate that the value doesn't contain unescaped quotes:

if (configVariable.properties?.type?.value === 'string' && !/^".*"$/.test(value)) {
    // Escape internal quotes
    value = `"${value.replace(/"/g, '\\"')}"`;
}
Suggested change
value = `"${value}"`;
value = JSON.stringify(value);

Copilot uses AI. Check for mistakes.
}
handleUpdateConfigValue(value, configVariable);
}

const getPlainValue = (value: string) => {
if (configVariable.properties?.type?.value === 'string' && /^".*"$/.test(value)) {
return value.replace(/^"|"$/g, '');
}
return value;
}
Comment on lines +132 to +139
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing semicolons after function declarations. While JavaScript/TypeScript allows this, it's inconsistent with the coding style used elsewhere in the file where semicolons are present after other statements. Add semicolons at the end of both function declarations for consistency.

Suggested change
}
const getPlainValue = (value: string) => {
if (configVariable.properties?.type?.value === 'string' && /^".*"$/.test(value)) {
return value.replace(/^"|"$/g, '');
}
return value;
}
};
const getPlainValue = (value: string) => {
if (configVariable.properties?.type?.value === 'string' && /^".*"$/.test(value)) {
return value.replace(/^"|"$/g, '');
}
return value;
};

Copilot uses AI. Check for mistakes.

const handleUpdateConfigValue = async (newValue: string, prevNode: ConfigVariable) => {
const newConfigVarNode: ConfigVariable = {
...prevNode,
Expand Down Expand Up @@ -304,13 +318,13 @@ export function ConfigurableItem(props: ConfigurableItemProps) {
return Math.min(5, Math.ceil(value.length / 100));
})()}
resize="vertical"
value={configVariable?.properties?.configValue?.value ? String(configVariable?.properties?.configValue?.value) : ''}
value={configVariable?.properties?.configValue?.value ? getPlainValue(String(configVariable?.properties?.configValue?.value)) : ''}
style={{
width: '100%',
maxWidth: '350px',
minHeight: '20px'
}}
onChange={(e: any) => handleUpdateConfigValue(e.target.value, configVariable)}
onChange={(e: any) => handleTextAreaChange(e.target.value)}
>
<style>{`
vscode-text-area::part(control) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { AddForm } from "../AddConfigurableVariables";
import { TopNavigationBar } from "../../../../components/TopNavigationBar";
import { TitleBar } from "../../../../components/TitleBar";
import ConfigurableItem from "../ConfigurableItem";
import { RelativeLoader } from "../../../../components/RelativeLoader";

const Container = styled.div`
width: 100%;
Expand Down Expand Up @@ -117,10 +118,12 @@ export function ViewConfigurableVariables(props?: ConfigProps) {
const [categoriesWithModules, setCategoriesWithModules] = useState<CategoryWithModules[]>([]);
const [selectedModule, setSelectedModule] = useState<PackageModuleState>(null);
const integrationCategory = `${props.org}/${props.package}`;
const [isLoading, setIsLoading] = useState<boolean>(false);

useEffect(() => {
getConfigVariables();
}, [props]);
setIsLoading(true);
getConfigVariables(true);
}, [props.org, props.package]);
Comment on lines 123 to +126
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The useEffect hook calls getConfigVariables(true) but doesn't include it in the dependency array. This can lead to stale closures where the function captures old values of state variables like rpcClient, selectedModule, etc.

Consider either:

  1. Wrapping getConfigVariables with useCallback and adding it to the dependency array
  2. Disabling the ESLint rule with a comment if the omission is intentional
  3. Refactoring to avoid the dependency by inlining the logic

Copilot uses AI. Check for mistakes.

useEffect(() => {
if (categoriesWithModules.length > 0 && !selectedModule) {
Expand Down Expand Up @@ -265,7 +268,7 @@ export function ViewConfigurableVariables(props?: ConfigProps) {
});
};

const getConfigVariables = async () => {
const getConfigVariables = async (initialLoad: boolean = false) => {

let data: ConfigVariablesState = {};
let errorMsg: string = '';
Expand All @@ -278,13 +281,13 @@ export function ViewConfigurableVariables(props?: ConfigProps) {
.then((variables) => {
data = (variables as any).configVariables;
errorMsg = (variables as any).errorMsg;
});
})
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid automated semicolon insertion (90% of all statements in the enclosing function have an explicit semicolon).

Suggested change
})
});

Copilot uses AI. Check for mistakes.

setConfigVariables(data);
setErrorMessage(errorMsg);

// Only set initial selected module if none is selected
if (!selectedModule) {
if (!selectedModule || initialLoad) {
// Extract and set the available categories with their modules
const extractedCategories = Object.keys(data).map(category => ({
name: category,
Expand All @@ -299,6 +302,7 @@ export function ViewConfigurableVariables(props?: ConfigProps) {
module: initialModule
});
}
setIsLoading(false);
};

const updateErrorMessage = (message: string) => {
Expand Down Expand Up @@ -354,7 +358,8 @@ export function ViewConfigurableVariables(props?: ConfigProps) {
/>
</SearchContainer>
<div style={{ width: "auto" }}>
<SplitView defaultWidths={[20, 80]}>
{isLoading && <RelativeLoader message="Loading configurable variables..." />}
{!isLoading && <SplitView defaultWidths={[20, 80]}>
{/* Left side tree view */}
<div id={`package-treeview`} style={{ padding: "10px 0 50px 0" }}>
{/* Display integration category first */}
Expand Down Expand Up @@ -669,7 +674,7 @@ export function ViewConfigurableVariables(props?: ConfigProps) {
}
</>
</div>
</SplitView>
</SplitView>}
</div>
</div>
</ViewContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,13 @@ export class MiDataMapperRpcManager implements MIDataMapperAPI {

async formatDMC(documentUri: string): Promise<void> {
const uri = Uri.file(documentUri);
const edits: TextEdit[] = await commands.executeCommand("vscode.executeFormatDocumentProvider", uri);
let edits: TextEdit[];
try {
edits = await commands.executeCommand("vscode.executeFormatDocumentProvider", uri);
} catch (error) {
console.error("Error occurred while formatting DMC file: ", error);
return;
}
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential issue: If edits is null or undefined (which can happen if no formatter is available for the document type), calling workspaceEdit.set(uri, edits) at line 181 may cause an error. Add a null check after the try-catch block:

if (!edits) {
    console.warn("No formatting edits returned for DMC file");
    return;
}
Suggested change
}
}
if (!edits) {
console.warn("No formatting edits returned for DMC file");
return;
}

Copilot uses AI. Check for mistakes.
const workspaceEdit = new WorkspaceEdit();
workspaceEdit.set(uri, edits);
await workspace.applyEdit(workspaceEdit);
Expand Down
Loading