preface

Vscode has become one of the indispensable development tools for the front end, and I think the popularity of vscode has a lot to do with its “do-it-all” plug-in system. In our work, we can use it to develop pure tool-based plug-ins, and we can also use it to develop some functional plug-ins combined with the company’s business. Here I share a plug-in that can intelligently remove unused variables by combining Babel. I hope it will be inspired and helpful for you to develop vscode plug-in.

The body of the

Today we will start by familiarizing ourselves with the setup process of vscode plug-in projects

1. Initialize a project using officially provided scaffolding

Erection of scaffolding

# NPM form
npm install -g yo generator-code
# yarn form
yarn global add yo generator-code
Copy the code

Running scaffold

# Run scaffolding
yo code
Copy the code

Select a template. For developers unfamiliar with TypeScript, we’ll use New Extension (JavaScript).

? What type of extension do you want to create? New Extension (JavaScript) ? What's the name of your extension? rm-unuse-var ? What's the identifier of your extension? rm-unuse-var ? What's the description of your extension? Remove unused variables? Enable JavaScript type checking in 'jsconfig.json'? Yes ? Initialize a git repository? Yes ? Which package manager to use? yarnCopy the code

This is the directory structure we eventually generated

Let’s run the plugin first

Click the “run” button above to open a new vscode window. Press Ctrl+Shift+P to enter “Hello World” in the new window. You will see a prompt box in the lower right corner of the window, indicating that our first vscode plug-in has been successfully run.

2. Customize commands, shortcut keys, and menus

Vscode plugin many functions are based on a command, we can customize some commands, this command will appear in the command list after pressing Ctrl+Shift+P, at the same time, you can configure the command shortcut key, configuration Explorer menu, editor menu, title menu, drop-down menu, icon in the upper right corner, etc.

3. How to add a command list

Package. json partial configuration

{
  // Extended activation events
  "activationEvents": ["onCommand:rm-unuse-var.helloWorld"].// Import file
  "main": "./extension.js".// Add directives
  "contributes": {
    "commands": [{// The value must be the same as that configured in activationEvents
        "command": "rm-unuse-var.helloWorld".// This is the name of our directive, you can change the value here and try to run the plugin again
        "title": "Hello World"}}}]Copy the code

Shortcuts are the easiest way to use them in development, so let’s modify the configuration to allow the plugin to work in this way.

{
  "contributes": {
    "commands": [{// The value must be the same as that configured in activationEvents
        "command": "rm-unuse-var.helloWorld".// This is the name of our directive, you can change the value here and try to run the plugin again
        "title": "Hello World"}].// Shortcut key binding
    "keybindings": [{"command": "rm-unuse-var.helloWorld"."key": "ctrl+6"."mac": "cmd+6"}}}]Copy the code

Let’s run it again and use the shortcut Ctrl+6 to see if our plugin works. Yes, it’s as simple as that, and our plugin already supports keyboard shortcuts.

4, Calling helloWorld is too boring, next let’s change the name of the directive

package.json

{
  "activationEvents": ["onCommand:rm-unuse-var.rm-js-var"]."main": "./extension.js"."contributes": {
    "commands": [{"command": "rm-unuse-var.rm-js-var"."title": "Hello World"}]."keybindings": [{"command": "rm-unuse-var.rm-js-var"."key": "ctrl+6"."mac": "cmd+6"}}}]Copy the code

Because we registered the name of the directive in extension.js, we need to synchronize the changes as well

let disposable = vscode.commands.registerCommand(
  "rm-unuse-var.rm-js-var".function () {
    vscode.window.showInformationMessage("Hello World from rm-unuse-var!"); });Copy the code

5, installation,babelRelated to the library

We can modify the code in three steps

3. Generate new code based on the modified AST syntax tree

Babel has libraries to handle all three of these steps

  1. @babel/parserGenerate AST syntax treeThe document address
  2. @babel/traverseWalk through the AST syntax treeThe document address
  3. @babel/generatorGenerate code from the AST syntax treeThe document address
  4. @babel/typesTool libraryThe document address

Let’s take a look at some of the basic uses of these libraries, such as implementing an es6 arrow function into a normal function

Transformation before

const say = () = > {
  console.log("hello");
};
Copy the code

After the transformation

function say() {
  console.log("hello");
}
Copy the code

Code implementation, code part write dead for learning reference only

const t = require("@babel/types");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generate = require("@babel/generator").default;
// 1. Parse code into an AST syntax tree
const ast = parser.parse(`const say = () => { console.log("hello"); }; `);
// 2. Modify AST syntax tree
traverse(ast, {
  VariableDeclaration(path) {
    const { node } = path;
    // Find the first statement
    const declaration = node.declarations[0];
    // The contents of the definition
    const init = declaration.init;
    // check if it is an arrow function
    if (t.isArrowFunctionExpression(init)) {
      // Replace the original expression with the newly generated functionpath.replaceWith( t.functionDeclaration( declaration.id, init.params, init.body, init.generator, init.async ) ); }}});// generate new code based on the modified AST syntax tree
console.log(generate(ast).code);
/* function say() { console.log("hello"); } * /
Copy the code

I’m sure many of you are wondering if our expression is relatively simple, but if it’s complicated, defining nesting can be very deep and complicated. How do you know which node to replace? . In fact, here you can use astExplorer.net/ which is an online conversion of AST website. We can open two Windows, putting the code before conversion in the first window and the interface to be converted in the second window. At this point we can compare the difference before and after the transformation to implement our code.

6. Think about how plug-ins are implemented.

Get the code of the currently open JS file in the editor. 2. Parse the code into the AST syntax tree. 3

We already know 2 and 4. Now we just need to see how 1, 3 and 5 can be implemented. Ok

1 and 5 we can use the methods provided by vscode

1, get the editor’s currently open JS file code

import * as vscode from "vscode";
// The currently open file
const { activeTextEditor } = vscode.window;
// Then we can get our code easily from getText under document
const code = activeTextEditor.document.getText();
Copy the code

5, replace the current JS file code

activeTextEditor.edit((editBuilder) = > {
  editBuilder.replace(
    // Since we want full file replacement, we need to define an interval from scratch
    new vscode.Range(
      new vscode.Position(0.0),
      new vscode.Position(activeTextEditor.document.lineCount + 1.0)),// Our new code
    generate(ast).code
  );
});
Copy the code

Ok, now we are left with the core step 3.

3. Traverse the AST syntax tree to delete unused definitions

Let’s start by looking at, what does an unused definition cover?

import vue from "vue";
const a = { test1: 1.test2: 2 };
const { test1, test2 } = a;
function b() {}
let c = () = > {};
var d = () = > {};
Copy the code

Then the online AST transforms the site and copies the content to see the generated syntax tree structure

Let’s implement an example first, such as removing unnecessary variables from the following code

Transformation before

var a = 1;
var b = 2;
console.log(a);
Copy the code

After the transformation

var a = 1;
console.log(a);
Copy the code
  • Scope.getbinding (name) gets all current bindings
  • Scope.getbinding (name). Referenced Whether the binding is referenced
  • Scope.getbinding (name). ConstantViolations Gets all current binding changes
  • Scope.getbinding (name). ReferencePaths Obtains all binding paths

Code implementation

const t = require("@babel/types");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generate = require("@babel/generator").default;

const ast = parser.parse(`var a = 1; var b = 2; console.log(a); `);

traverse(ast, {
  VariableDeclaration(path) {
    const { node } = path;
    const { declarations } = node;
    // const a = 1,b = 2; This scenario
    node.declarations = declarations.filter((declaration) = > {
      const { id } = declaration;
      // const { b, c } = a;
      if (t.isObjectPattern(id)) {
        // path.scope.getBinding(name). Referenced Determine whether a variable is referenced
        // Filter to remove unused variables
        id.properties = id.properties.filter((property) = > {
          const binding = path.scope.getBinding(property.key.name);
          return!!!!! binding? .referenced; });// If all variables in the object are not applied, the object is removed entirely
        return id.properties.length > 0;
      } else {
        // const a = 1;
        const binding = path.scope.getBinding(id.name);
        return!!!!! binding? .referenced; }});// If the entire definition statement is not referenced, the entire definition statement is removed
    if (node.declarations.length === 0) { path.remove(); }}});console.log(generate(ast).code);
Copy the code

7. Now that you understand the basic process, let’s look at the final code implementation

const t = require("@babel/types");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generate = require("@babel/generator").default;

const ast = parser.parse(
  `import vue from 'vue'; var a = 1; var b = 2; var { test1, test2 } = { test1: 1, test2: 2 }; function c(){} function d(){} d(); console.log(a, test1); `,
  {
    sourceType: "module"}); traverse(ast, {// Handle const var let
  VariableDeclaration(path) {
    const { node } = path;
    const { declarations } = node;

    node.declarations = declarations.filter((declaration) = > {
      const { id } = declaration;
      if (t.isObjectPattern(id)) {
        id.properties = id.properties.filter((property) = > {
          const binding = path.scope.getBinding(property.key.name);
          return!!!!! binding? .referenced; });return id.properties.length > 0;
      } else {
        const binding = path.scope.getBinding(id.name);
        return!!!!! binding? .referenced; }});if (node.declarations.length === 0) { path.remove(); }},/ / handle the import
  ImportDeclaration(path) {
    const { node } = path;
    const { specifiers } = node;
    if(! specifiers.length) {return;
    }
    node.specifiers = specifiers.filter((specifier) = > {
      const { local } = specifier;
      const binding = path.scope.getBinding(local.name);
      return!!!!! binding? .referenced; });if (node.specifiers.length === 0) { path.remove(); }},/ / processing function
  FunctionDeclaration(path) {
    const { node } = path;
    const { id } = node;
    const binding = path.scope.getBinding(id.name);
    if(! binding? .referenced) { path.remove(); }}});console.log(generate(ast).code);
Copy the code

8. Vscode sets the limit that our plugin only supports js files

Since our current implementation is for JS files, we can disable our shortcut keys when opening other types of files. We can modify package.json package.json

{
  "contributes": {
    "commands": [{"command": "rm-unuse-var.remove"."title": "Hello World"}]."keybindings": [{"command": "rm-unuse-var.remove"."key": "ctrl+6"."mac": "cmd+6"."when": "resourceLangId == javascript"}}}]Copy the code

9. Integrate into the project we created earlier

Omitted here… I believe that we have seen the above introduction is fully capable of their own integration

10. Package and publish plug-ins

For packaging we can use vsCE tools

Install VSCE globally

# npm
npm i vsce -g
# yarn
yarn global add vsce
Copy the code

Packaging plug-in

Modify the readme. md file before packaging otherwise an error will be reported

vsce package
Copy the code

After execution, a.vsix file is generated

You can import it directly if you want to use it locally with vscode

If you want to release to the market, we need to register account code.visualstudio.com/api/working…

# Login account
vsce login your-publisher-name
# release
vsce publish
Copy the code

Released after successful can saw in our market marketplace.visualstudio.com/items?itemN… You can also search for our plugin in vscode

conclusion

So far, I believe you have an understanding of the basic process of vscode plug-in development.

If you find this article helpful, you can like 😊

Of course, there are a lot of configuration of vscode plug-in that has not been introduced. If you have time later, you can organize it into a separate article to introduce

If you have problems in the development process or other front-end technical problems, you can also add my wechat RJJS1221 communication, or directly reply in the comments section.

Source address github.com/taoxhsmile/…