### Install Dependencies Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md Run this command in the root folder to install project dependencies before starting the workshop. ```bash yarn install ``` -------------------------------- ### Example HTML with data-test attributes Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This is an example of a component invocation with attributes that need to be removed. ```handlebars ``` -------------------------------- ### Initial ESLint Rule: Report All Properties Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This is the starting point for the ESLint rule, reporting all Property nodes encountered. It serves as a basic structure before applying specific checks. ```javascript return { Property(node) { context.report({ node, message: 'Unnecessary injection argument', }); } }; ``` -------------------------------- ### Ember Service Injection Example Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md Demonstrates an Ember component with a service injection. The 'search' argument is unnecessary as it matches the property key. ```javascript Component.extend({ search: service('search'), }) ``` -------------------------------- ### Traverse AST for Block Statements Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This snippet shows the basic structure for traversing an AST and visiting BlockStatement nodes. It serves as a starting point for more specific checks. ```javascript glimmer.traverse(root, { BlockStatement(node) { // TODO check for the other requirements and warn if they match }, }); ``` -------------------------------- ### Linting Results Example Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This output shows the results of running ember-template-lint with a custom rule, indicating detected 'unless/else' errors in specific files and line numbers. ```text $ yarn -s lint:hbs app/components/crate-toml-copy.hbs 6:8 error Found unless/else custom/no-unless-else app/templates/crate/owners.hbs 60:8 error Found unless/else custom/no-unless-else ✖ 2 problems (2 errors, 0 warnings) ``` -------------------------------- ### Handlebars Template Example Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This is a basic Handlebars template structure. It includes a div with a title, body, and a button that triggers an 'on' element modifier. ```handlebars

{{title}}

{{body}}
``` -------------------------------- ### Element Modifier with Named Parameter Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md Example demonstrating the 'on' element modifier in Handlebars with a named parameter 'passive'. This shows how to pass additional options to modifiers. ```handlebars {{on "click" this.onNext passive=true}} ``` -------------------------------- ### Remove data-test-* attributes by returning null Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This AST transform visitor targets AttrNodes and removes them by returning null if their name starts with 'data-test-'. This is one method for deleting nodes from the AST. ```javascript module.exports = function() { return { name: 'ast-transform', visitor: { AttrNode(node) { if (node.name.startsWith('data-test-')) { return null; } } } }; }; ``` -------------------------------- ### Remove data-test-* attributes by filtering ElementNode attributes Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This AST transform visitor targets ElementNodes and filters their attributes, removing any AttrNode whose name starts with 'data-test-'. This approach directly modifies the attributes array of the element. ```javascript module.exports = function() { return { name: 'ast-transform', visitor: { ElementNode(node) { node.attributes = node.attributes .filter(it => !it.name.startsWith('data-test-')); } } }; }; ``` -------------------------------- ### Parse, Modify, and Print Template AST Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md Demonstrates the complete cycle of reading a template, parsing it into an AST, performing modifications (placeholder), converting the AST back to text, and writing changes back to the file. ```javascript for (let templatePath of templatePaths) { // read the file content let template = fs.readFileSync(templatePath, 'utf8'); // parse the file content into an AST let root = recast.parse(template); // TODO modify the AST // convert the AST back into text let newTemplate = recast.print(root); // if necessary, write the changes back to the original file if (newTemplate !== template) { fs.writeFileSync(templatePath, newTemplate, 'utf8') } } ``` -------------------------------- ### Initial Script Output Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md The expected output after running the `count-tags.js` script before any tag counting logic is implemented. ```text Map {} ``` -------------------------------- ### Basic ESLint Rule Structure Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This is the initial structure for a custom ESLint rule. The `create` method is expected to return a visitor object. ```javascript module.exports = { create: function(context) { // TODO write your implementation here } }; ``` -------------------------------- ### Full Script to Find Unless/Else in Templates Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This complete Node.js script reads all .hbs files in the 'app/' directory, parses them into ASTs, and traverses each AST to find and log 'unless/else' patterns. It requires 'fs', 'globby', and '@glimmer/syntax' packages. ```javascript const fs = require('fs'); const globby = require('globby'); const glimmer = require('@glimmer/syntax'); // find all template files in the `app/` folder let templatePaths = globby.sync('app/**/*.hbs', { cwd: __dirname, absolute: true, }); for (let templatePath of templatePaths) { // read the file content let template = fs.readFileSync(templatePath, 'utf8'); // parse the file content into an AST let root = glimmer.preprocess(template); // use `traverse()` to "visit" all of the nodes in the AST glimmer.traverse(root, { BlockStatement(node) { if ( // first we make sure that `node.path` is really a `PathExpression` // since there are a few edge cases where it might not be node.path.type === 'PathExpression' && // then we check if this is an `unless` block node.path.original === 'unless' && // and finally, we check if the block has an `else` block too node.inverse ) { // if so, we print a warning to the console console.log(`Found unless/else in ${templatePath}:${node.loc.start.line}:${node.loc.start.column}`); } }, }); } ``` -------------------------------- ### Run Tag Counting Script Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md Execute the Node.js script to analyze Handlebars templates and count HTML tag and component usage. The initial output shows an empty map before implementation. ```bash node count-tags.js ``` -------------------------------- ### Traverse AST Nodes Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md Demonstrates how to use glimmer.traverse to visit specific AST node types like ElementNode and AttrNode. Useful for inspecting AST structure. ```javascript glimmer.traverse(root, { ElementNode(node) { console.log(node); }, AttrNode(node) { console.log(node); }, }); ``` -------------------------------- ### Swap Block Contents (Workaround) Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This code implements the workaround for swapping the contents of 'program' and 'inverse' blocks within a Handlebars AST node. It swaps the 'body' arrays of the program and inverse blocks to correctly exchange their content. ```javascript let programBody = node.program.body; let inverseBody = node.inverse.body; node.program.body = inverseBody; node.inverse.body = programBody; ``` -------------------------------- ### ESLint Visitor for Call Expressions Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This snippet demonstrates how to implement a visitor for `CallExpression` nodes in ESLint. It reports a generic message for any call expression found. ```javascript return { CallExpression(node) { context.report({ node, message: 'Unexpected console.log() expression', }); } }; ``` -------------------------------- ### ESLint Rule: No Console Log Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This code implements a basic ESLint rule that reports unexpected `console.log()` expressions. It is suitable for beginners learning to create ESLint rules. ```javascript context.report({ node, message: 'Unexpected console.log() expression', }); } }; } }; ``` -------------------------------- ### Count Handlebars Tag Occurrences Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md Parses all .hbs files in the app directory, traverses the AST to count each ElementNode's tag, and logs the final counts. Requires globby, fs, and @glimmer/syntax. ```javascript const fs = require('fs'); const globby = require('globby'); const glimmer = require('@glimmer/syntax'); let tagCounter = new Map(); // find all template files in the `app/` folder let templatePaths = globby.sync('app/**/*.hbs', { cwd: __dirname, absolute: true, }); for (let templatePath of templatePaths) { // read the file content let template = fs.readFileSync(templatePath, 'utf8'); // parse the file content into an AST let root = glimmer.preprocess(template); // use `traverse()` to "visit" all of the nodes in the AST glimmer.traverse(root, { ElementNode(node) { // read the tag name from the `ElementNode` let { tag } = node; // read the current count for that tag name let previousCount = tagCounter.get(tag) || 0; // increase the counter by 1 tagCounter.set(tag, previousCount + 1); } }); } // output the raw results console.log(tagCounter); ``` -------------------------------- ### Parse Template Files into AST Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md Reads template files and parses their content into an Abstract Syntax Tree (AST) using ember-template-recast. This is the initial step for any AST manipulation. ```javascript for (let templatePath of templatePaths) { // read the file content let template = fs.readFileSync(templatePath, 'utf8'); // parse the file content into an AST let root = recast.parse(template); } ``` -------------------------------- ### Full Codemod Solution for Ember.js Templates Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This snippet finds all `.hbs` files in the `app/` directory, parses their content, traverses the AST to rename `MenuItem` components to `NewMenuItem`, moves the `@caption` attribute value to children, removes the `@caption` attribute, and writes the modified template back to the file if changes were made. It requires `fs`, `globby`, and `ember-template-recast`. ```javascript const fs = require('fs'); const globby = require('globby'); const recast = require('ember-template-recast'); // find all template files in the `app/` folder let templatePaths = globby.sync('app/**/*.hbs', { cwd: __dirname, absolute: true, }); for (let templatePath of templatePaths) { // read the file content let template = fs.readFileSync(templatePath, 'utf8'); // parse the file content into an AST let root = recast.parse(template); // use `traverse()` to "visit" all of the nodes in the AST recast.traverse(root, { ElementNode(node) { // filter out non-MenuItem elements if (node.tag !== 'MenuItem') return; // change the tag name to `NewMenuItem` node.tag = 'NewMenuItem'; // find the `@caption` attribute let captionAttr = node.attributes.find(it => it.name === '@caption'); if (captionAttr) { // move the `@caption` value into the element's `children` node.children = [captionAttr.value]; } // remove `@caption` attribute node.attributes = node.attributes.filter(it => it.name !== '@caption'); } }); // convert the AST back into text let newTemplate = recast.print(root); // if necessary, write the changes back to the original file if (newTemplate !== template) { fs.writeFileSync(templatePath, newTemplate, 'utf8') } } ``` -------------------------------- ### Identify Unless Blocks with Else Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This visitor function identifies 'unless' block statements that also have an 'inverse' (else) block. It ensures the node path is a PathExpression before checking the original name and the presence of an inverse block. ```javascript recast.traverse(root, { BlockStatement(node) { if ( // first we make sure that `node.path` is really a `PathExpression` // since there are a few edge cases where it might not be node.path.type === 'PathExpression' && // then we check if this is an `unless` block node.path.original === 'unless' && // and finally, we check if the block has an `else` block too node.inverse ) { // TODO transform this node } } }); ``` -------------------------------- ### Full Codemod for Unless/Else Transformation Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This script finds all Handlebars template files, parses them, traverses the AST to find 'unless' blocks with 'else' clauses, swaps their contents, changes 'unless' to 'if', and writes the modified templates back. It includes necessary imports for file system operations, globbing, and ember-template-recast. ```javascript const fs = require('fs'); const globby = require('globby'); const recast = require('ember-template-recast'); // find all template files in the `app/` folder let templatePaths = globby.sync('app/**/*.hbs', { cwd: __dirname, absolute: true, }); for (let templatePath of templatePaths) { // read the file content let template = fs.readFileSync(templatePath, 'utf8'); // parse the file content into an AST let root = recast.parse(template); // use `traverse()` to "visit" all of the nodes in the AST recast.traverse(root, { BlockStatement(node) { if ( // first we make sure that `node.path` is really a `PathExpression` // since there are a few edge cases where it might not be node.path.type === 'PathExpression' && // then we check if this is an `unless` block node.path.original === 'unless' && // and finally, we check if the block has an `else` block too node.inverse ) { let { program, inverse } = node; let programBody = program.body; let inverseBody = inverse.body; // swap `program` and `inverse` blocks node.program.body = inverseBody; node.inverse.body = programBody; // change the block statement from `unless` to `if` node.path.original = 'if'; } } }); // convert the AST back into text let newTemplate = recast.print(root); // if necessary, write the changes back to the original file if (newTemplate !== template) { fs.writeFileSync(templatePath, newTemplate, 'utf8') } } ``` -------------------------------- ### AST Transformation: Collapse Whitespace in TextNodes Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This AST plugin targets TextNodes and replaces collapsible whitespace within their 'chars' attribute with a single space. It's designed for use with the AST Explorer's glimmer transform. ```javascript module.exports = function() { return { name: 'ast-transform', visitor: { TextNode(node) { node.chars = node.chars.replace(/[ \r\n]+/g, ' '); } } }; }; ``` -------------------------------- ### ESLint Rule: Check Property and CallExpression Types Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md Refines the ESLint rule by checking if the Property's key is an Identifier and its value is a CallExpression. This narrows down the potential candidates for unnecessary arguments. ```javascript let { key, value } = node; if (key.type !== 'Identifier') return; if (value.type !== 'CallExpression') return; ``` -------------------------------- ### ESLint Rule: Check Service/Inject Callee Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md Further refines the ESLint rule by verifying that the CallExpression's callee is an Identifier named 'service' or 'inject'. This ensures we are targeting the correct Ember injection patterns. ```javascript let { callee } = value; if (callee.type !== 'Identifier') return; if (!['inject', 'service'].includes(callee.name)) return; ``` -------------------------------- ### ESLint Rule: Report Unnecessary Argument Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md Once all conditions are met, this code instructs ESLint to report the unnecessary injection argument, highlighting the specific Literal node. ```javascript context.report({ node: arg, message: 'Unnecessary injection argument', }); ``` -------------------------------- ### Remove Test Selectors from HTML Source: https://github.com/mainmatter/ast-workshop/blob/master/05b-strip-test-selectors/tests/index.html Use AST to parse HTML and remove elements with data-testid attributes. ```javascript import { parse } from "@babel/parser"; import traverse from "@babel/traverse"; import generate from "@babel/generator"; const html = `

Some text

User Name
`; const ast = parse(html, { sourceType: "module", plugins: ["jsx"], }); traverse(ast, { enter(path) { if ( path.isJSXOpeningElement() && path.node.attributes.some( (attr) => attr.type === "JSXAttribute" && attr.name.name === "data-testid" ) ) { path.remove(); } }, }); const { code } = generate(ast); console.log(code); ``` -------------------------------- ### Change Unless to If Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This snippet demonstrates how to change an 'unless' block statement to an 'if' statement by modifying the 'original' property of the node's path. ```javascript node.path.original = 'if'; ``` -------------------------------- ### Refining ESLint Rule for console.log Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This code refines the ESLint rule to specifically target `console.log()` calls by checking the AST node types and names. ```javascript module.exports = { create: function(context) { return { CallExpression(node) { let { callee } = node; if (callee.type !== 'MemberExpression') return; let { object, property } = callee; if (object.type !== 'Identifier' || object.name !== 'console') return; if (property.type !== 'Identifier' || property.name !== 'log') return; } }; } }; ``` -------------------------------- ### Collapse Whitespace with Regex Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md Use this regular expression to replace all collapsible whitespace characters (spaces, newlines, carriage returns, tabs) with a single space. The 'g' flag ensures all occurrences are replaced. ```javascript let newText = oldText.replace(/[ \n\r\t]+/g, ' '); ``` -------------------------------- ### Custom Ember Template Lint Rule for No Unless Else Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This JavaScript code defines a custom ember-template-lint rule. It uses the visitor pattern to detect and report 'unless' blocks that also have an 'else' block. ```javascript const Rule = require('ember-template-lint').Rule; module.exports = class extends Rule { visitor() { return { BlockStatement(node) { if ( // first we make sure that `node.path` is really a `PathExpression` // since there are a few edge cases where it might not be node.path.type === 'PathExpression' && // then we check if this is an `unless` block node.path.original === 'unless' && // and finally, we check if the block has an `else` block too node.inverse ) { // if so, report a template-lint warning this.log({ message: 'Found unless/else', line: node.loc && node.loc.start.line, column: node.loc && node.loc.start.column, source: this.sourceForNode(node) }); } }, }; } }; ``` -------------------------------- ### Check for Unless Block with Else Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This code segment checks if a given BlockStatement node represents an 'unless' block and if it has an associated 'else' block (inverse attribute is not null). ```javascript if ( // first we make sure that `node.path` is really a `PathExpression` // since there are a few edge cases where it might not be node.path.type === 'PathExpression' && // then we check if this is an `unless` block node.path.original === 'unless' && // and finally, we check if the block has an `else` block too node.inverse ) { // TODO print warning } ``` -------------------------------- ### ESLint Rule: Check Literal Argument Value Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md This part of the ESLint rule checks if the first argument of the CallExpression is a Literal and if its value matches the name of the Property key. This is the final condition to identify an unnecessary injection argument. ```javascript let arg = value.arguments[0]; if (!arg) return; if (arg.type !== 'Literal') return; if (arg.value !== key.name) return; ``` -------------------------------- ### Traverse AST to Modify Element Tag Names Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md Uses the `traverse` function to find all `ElementNode`s in the AST and modifies their tag names. This snippet specifically targets and renames 'MenuItem' components to 'NewMenuItem'. ```javascript recast.traverse(root, { ElementNode(node) { // filter out non-MenuItem elements if (node.tag !== 'MenuItem') return; // change the tag name to `NewMenuItem` node.tag = 'NewMenuItem'; }, }); ``` -------------------------------- ### Find and Move Caption Attribute Value Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md Locates the '@caption' attribute within an `ElementNode`'s attributes and moves its value to the `children` array of the `ElementNode`. This transforms an attribute into a child element. ```javascript // find the `@caption` attribute let captionAttr = node.attributes.find(it => it.name === '@caption'); if (captionAttr) { // move the `@caption` value into the element's `children` node.children = [captionAttr.value]; } ``` -------------------------------- ### Remove Caption Attribute Source: https://github.com/mainmatter/ast-workshop/blob/master/README.md Filters the `attributes` array of an `ElementNode` to remove the '@caption' attribute. This is done after its value has been moved to the `children` array to avoid duplication. ```javascript // remove `@caption` attribute node.attributes = node.attributes.filter(it => it.name !== '@caption'); ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.