Use Synvert to automatically add autoComplete attribute to JSX project

The HTML autocomplete attribute lets web developers specify what if any permission the user agent has to provide automated assistance in filling out form field values, as well as guidance to the browser as to the type of information expected in the field. See more here.

I get a task to add autocomplete to an existing react project. In this react project, we have some react code that looks like this:

<Field name="email" type="email" />
<Field name="password" type="password" />
<Field name="new_password" type="password" />

We want to add autoComplete attribute to the Field elements, so the code will become:

<Field name="email" type="email" autoComplete="email" />
<Field name="password" type="password" autoComplete="current-password" />
<Field name="new_password" type="password" autoComplete="new-password" />

It’s time to use Synvert to automatically convert the code. First, I open the Synvert application, set the file pattern as **/*.jsx, the input as <Field name="email" type="email" /> , and the output as <Field name="email" type="email" autoComplete="email" />, then click the “Generate Snippet” button, it will generate the snippet as follows:

const Synvert = require("synvert-core");

new Synvert.Rewriter("group", "name", () => {
  withinFiles("**/*.jsx", () => {
    findNode(`.JsxSelfClosingElement
      [tagName=Field]
      [attributes=.JsxAttributes
        [properties.length=2]
        [properties.0=.JsxAttribute[name=name][initializer=.StringLiteral[text=email]]]
        [properties.1=.JsxAttribute[name=type][initializer=.StringLiteral[text=email]]]
      ]`, () => {
      insert(' autoComplete="email"', { to: "attributes.properties.-1", at: "end" });
    });
  });
});

As you can see, it finds a Field element who has 2 properties, the first property’s name is name and value is email, the second property’s name is type and value is email, then insert autoComplete="email" to the end of the last property. But obviously, this is not what we expect.

We want to find a Field element, no matter how many properties it has, so remove [properties.length=2].

As long as it has a property whose name is name and value is email, no matter its position, so replace .0= with includes.

Same as the type property, replace .1= with includes.

Then the snippet becomes:

const Synvert = require("synvert-core");

new Synvert.Rewriter("group", "name", () => {
  withinFiles("**/*.jsx", () => {
    findNode(`.JsxSelfClosingElement
      [tagName=Field]
      [attributes=.JsxAttributes
        [properties includes .JsxAttribute[name=name][initializer=.StringLiteral[text=email]]]
        [properties includes .JsxAttribute[name=type][initializer=.StringLiteral[text=email]]]
      ]`, () => {
      insert(' autoComplete="email"', { to: "attributes.properties.-1", at: "end" });
    });
  });
});

Now we can search in the project, it finds the email Field, and inserts autoComplete="email", then we can click the “Replace” icon to add the autoComplete attribute.

Okay, we have added the autoComplete attribute, what about we search again?

Oh, that’s bad, it finds that Field again and tries to add the duplicated autoComplete attribute.

Let’s fix it, we need to add a filter that it does not have a property whose name is autoComplete, then the snippet becomes:

const Synvert = require("synvert-core");

new Synvert.Rewriter("group", "name", () => {
  withinFiles("**/*.jsx", () => {
    findNode(`.JsxSelfClosingElement
      [tagName=Field]
      [attributes=.JsxAttributes
        [properties not includes .JsxAttribute[name=autoComplete]]
        [properties includes .JsxAttribute[name=name][initializer=.StringLiteral[text=email]]]
        [properties includes .JsxAttribute[name=type][initializer=.StringLiteral[text=email]]]
      ]`, () => {
      insert(' autoComplete="email"', { to: "attributes.properties.-1", at: "end" });
    });
  });
});

Then we click the “Search” button, and no file is affected by the snippet, cool.

So far the snippet works well for email Field, let’s add autoComplte to support password Fields.

Synvert snippet is a javascript code, so we just need to make the email to be a variable. So the new snippet becomes:

const Synvert = require("synvert-core");

new Synvert.Rewriter("group", "name", () => {
  const PATTERNS = [
    { name: "email", type: "email", autoComplete: "email" },
    { name: "password", type: "password", autoComplete: "current-password" },
    { name: "new_password", type: "password", autoComplete: "new-password" },
  ];
  withinFiles("**/*.jsx", () => {
    for (const pattern of PATTERNS) {
      findNode(`.JsxSelfClosingElement
        [tagName=Field]
        [attributes=.JsxAttributes
          [properties not includes .JsxAttribute[name=autoComplete]]
          [properties includes .JsxAttribute[name=name][initializer=.StringLiteral[text=${pattern.name}]]]
          [properties includes .JsxAttribute[name=type][initializer=.StringLiteral[text=${pattern.type}]]]
        ]`, () => {
        insert(` autoComplete="${pattern.autoComplete}"`, { to: "attributes.properties.-1", at: "end" });
      });
    }
  });
});

Then we click the “Search” button, it finds the password Fields.

We can add autoComplete attributes to these fields by clicking the “Replace All” button.

If you want to support more field types, just add more patterns to the PATTERNS constant. e.g.

const PATTERNS = [
  { name: "email", type: "email", autoComplete: "email" },
  { name: "password", type: "password", autoComplete: "current-password" },
  { name: "new_password", type: "password", autoComplete: "new-password" },
  { name: "cvv", type: "text", autoComplete: "off" },
];

The official snippet is here, it takes care of the input tag and uses insertAfter for multi-line Fields.

0 Comments
Authors
Richard Huang