Search code examples
javascriptpythonvisual-studio-codevscode-extensionsformatter

Why my formatter doesn't work when I activate it? `unexpected token (1:5)` error while trying to format


I am developing a code formatter and snippet support for Mount&Blade: Warband Script Language which is a game and I have encountered serious problems which my formatter doesn't work at all. Snippets works as intended. I explained what I intended to do at the bottom of this post. Here is my work for

extension.js

const acorn = require('acorn');
const escodegen = require('escodegen');
const vscode = require('vscode');

function formatWarbandScriptLanguageCode(code) {
    const parsedAst = acorn.parse(code, { ecmaVersion: 5 });

    let indentationLevel = 0;

    traverse(parsedAst, {
        enter(node, parent) {
            if (node.type === 'CallExpression') {
                const operationNames = [
                    'try_begin',
                    'try_for_range',
                    'try_for_range_backwards',
                    'try_for_parties',
                    'try_for_agents',
                    'try_for_prop_instances',
                    'try_for_players',
                    'try_for_dict_keys',
                ];

                if (operationNames.includes(node.callee.name)) {
                    // Insert a newline before the operation call
                    if (parent.body.indexOf(node) === 0) {
                        const newlineNode = {
                            type: 'WhiteSpace',
                            value: '\n' + '    '.repeat(indentationLevel), // Adjust the desired indentation
                        };
                        parent.body.unshift(newlineNode);
                    }

                    // Add a tab indentation to the arguments of the operation
                    node.arguments.forEach(arg => {
                        if (arg.type === 'ArrayExpression') {
                            arg.elements.forEach(element => {
                                element.loc.indent += 1; // Adjust the indentation level
                            });
                        }
                    });

                    indentationLevel++;
                }
            }
        },
        leave(node) {
            if (node.type === 'CallExpression') {
                const operationNames = [
                    'try_begin',
                    'try_for_range',
                    'try_for_range_backwards',
                    'try_for_parties',
                    'try_for_agents',
                    'try_for_prop_instances',
                    'try_for_players',
                    'try_for_dict_keys',
                ];

                if (operationNames.includes(node.callee.name)) {
                    indentationLevel--;
                }
            }
        },
    });

    const formattedCode = escodegen.generate(parsedAst);
    return formattedCode;
}

function activate(context) {
    console.log('M&B Warband API extension is now active.');
    // ... other activation code ...

    let disposable = vscode.commands.registerCommand('mbap.formatWarbandScript', () => {
        const editor = vscode.window.activeTextEditor;
        if (!editor) {
            return;
        }

        const document = editor.document;
        const text = document.getText();

        // Format the code
        const formattedCode = formatWarbandScriptLanguageCode(text);

        // Apply the formatted code
        const edit = new vscode.TextEdit(
            new vscode.Range(0, 0, document.lineCount, 0),
            formattedCode
        );

        const workspaceEdit = new vscode.WorkspaceEdit();
        workspaceEdit.set(document.uri, [edit]);
        vscode.workspace.applyEdit(workspaceEdit);
    });

    context.subscriptions.push(disposable);
}

// This method is called when your extension is deactivated
function deactivate() {
    console.log('M&B Warband API extension is now deactivated.');
}

module.exports = {
    activate,
    deactivate
};

package.json

{
    "name": "mbap",
    "displayName": "M&B: Warband API",
    "description": "Mount & Blade: Warband language support for Microsoft Visual Studio Code by Azremen and Sart",
    "publisher": "Azremen",
    "version": "0.1.18",
    "homepage": "https://github.com/Azremen/MB-Warband-API-VSC/blob/main/README.md",
    "engines": {
        "vscode": "^1.62.0"
    },
    "repository": {
        "type": "git",
        "url": "https://github.com/Azremen/MB-Warband-API-VSC.git"
    },
    "categories": [
        "Programming Languages"
    ],
    "main": "./extension.js",
    "activationEvents": [
        "onCommand:mbap.formatWarbandScript"
    ],
    "contributes": {
        "languages": [
            {
                "id": "warbandsl",
                "aliases": [
                    "Warband Script Language"
                ],
                "extensions": [
                    ".py"
                ],
                "configuration": "./language-configuration.json"
            }
        ],
        "commands": [
            {
                "command": "mbap.formatWarbandScript",
                "title": "Format Warband Script Language Code"
            }
        ],
        "snippets": [
            {
                "language": "warbandsl",
                "path": "./snippets/mbap.code-snippets"
            }
        ],
        "keybindings": [
            {
                "command": "mbap.formatWarbandScript",
                "key": "alt+shift+f",
                "when": "editorTextFocus && resourceLangId == warbandsl"
            }
        ]
    },
    "configuration": {
        "title": "Warband Script Language Formatter",
        "properties": {
            "warbandsl.indentation": {
                "type": "string",
                "enum": [
                    "tabs",
                    "spaces"
                ],
                "default": "spaces",
                "description": "Indentation style for Warband Script Language Formatter."
            },
            "warbandsl.tabSize": {
                "type": "number",
                "default": 4,
                "description": "Number of spaces or tabs to use for indentation."
            }
        }
    },
    "dependencies": {
        "acorn": "^8.0.1",
        "escodegen": "^2.0.0"
    }
}

Visual Studio Code gives unexpected token(1:5) error with a pop-up on combination of keyboard keys of alt+shift+f which it is supposed to format the desired file. Visual Studio Code tries to run mbap.formatWarbandScript command while this is happening as far as I understand.

I was intending it to move onto the next line after operationNames array for each element and then insert a tab. After they are done there is a 'try_end' line which is supposed to align with those. I can't test it out since it doesn't work at all.


Solution

  • After reading comments and simplifying my problem I've come to conclusion as I have reworked my project entirely here is my work:

    extension.js

    const vscode = require('vscode');
    const { execFileSync } = require('child_process');
    const os = require('os');
    const fs = require('fs');
    
    function formatAndSaveDocument() {
        const activeEditor = vscode.window.activeTextEditor;
    
        if (activeEditor) {
            const document = activeEditor.document;
            const operationNames = [
                "try_begin",
                "try_for_range",
                "try_for_range_backwards",
                "try_for_parties",
                "try_for_agents",
                "try_for_prop_instances",
                "try_for_players",
                "try_for_dict_keys",
                "else_try",
            ];
    
            const originalContent = document.getText();
    
            // Create a temporary file to store the original content
            const tempFilePath = `${os.tmpdir()}/temp_mbap_script.py`;
            fs.writeFileSync(tempFilePath, originalContent, 'utf-8');
    
            // Use black command-line tool to format the content and get the formatted code
            const blackCmd = `black --quiet ${tempFilePath}`;
            execFileSync(blackCmd, {
                encoding: 'utf-8',
                shell: true
            });
    
            // Read the black-formatted content from the temporary file
            const blackFormattedCode = fs.readFileSync(tempFilePath, 'utf-8');
    
            // Delete the temporary file
            fs.unlinkSync(tempFilePath);
    
            // Apply your custom formatting with adjusted indentation levels for specific operation names
            const customFormattedLines = [];
            let currentIndentationLevel = 0;
    
            for (const line of blackFormattedCode.split('\n')) {
                const trimmedLine = line.trim();
    
                if (trimmedLine.includes("try_end") || trimmedLine.includes("else_try")) {
                    currentIndentationLevel--;
                }
    
                const customIndentation = '\t'.repeat(Math.max(0, currentIndentationLevel));
                const customFormattedLine = customIndentation + line;
                customFormattedLines.push(customFormattedLine);
    
                if (operationNames.some(op => trimmedLine.includes(op))) {
                    currentIndentationLevel++;
                }
            }
    
            const customFormattedCode = customFormattedLines.join('\n');
    
            const edit = new vscode.WorkspaceEdit();
            edit.replace(document.uri, new vscode.Range(0, 0, document.lineCount, 0), customFormattedCode);
    
            vscode.workspace.applyEdit(edit).then(success => {
                if (success) {
                    vscode.window.showInformationMessage('Formatted and saved the document.');
                } else {
                    vscode.window.showErrorMessage('An error occurred while formatting and saving the document.');
                }
            });
        }
    }
    
    function checkAndInstallBlack() {
        const terminal = vscode.window.createTerminal('Install Black');
        
        // Check if Black is installed
        terminal.sendText('pip show black', true);
    
        // When the terminal output is ready, check if Black is installed
        terminal.onDidWriteData(data => {
            if (data.includes("Package(s) not found: black")) {
                // Black is not installed, so install it
                terminal.sendText('pip install black', true);
            }
            
            // Dispose of the terminal
            terminal.dispose();
        });
    
        terminal.show();
    }
    
    function activate(context) {
        // Register the formatAndSaveDocument command
        const disposable = vscode.commands.registerCommand('mbap.formatWarbandScript', formatAndSaveDocument);
    
        // Run the checkAndInstallBlack function when the extension is activated
        checkAndInstallBlack();
    
        // Add the disposable to the context for cleanup
        context.subscriptions.push(disposable);
    }
    
    exports.activate = activate;
    
    

    I tried to add comment lines as I have progressed. I hope this helps anyone who is trying to do something like that in any way.

    For those who are interested in what I did, It was put the file in a default formatter for python externally and get it in a temporary file. After formatting it in temporary file get the contents then delete the temporary file and proceed with my logic of formatting on that formatted version with keeping indentation on It's lines but adjust my operationNames accordingly to my liking. In extra, try_end was end of if statement in the Warband Scripting Language so I did an exception for that as you may have noticed with else_try. Then I pushed these to the format and run those for the document. If I explained wrong please let me know in the comments. Therefore thank you all!