Skip to content

Commit

Permalink
Support <template> (no-undef, etc) (#1395)
Browse files Browse the repository at this point in the history
Co-authored-by: Bryan Mishkin <698306+bmish@users.noreply.github.com>
Co-authored-by: Nathaniel Furniss <nlfurniss@gmail.com>
  • Loading branch information
3 people authored Oct 27, 2022
1 parent 9f4d002 commit 9864569
Show file tree
Hide file tree
Showing 6 changed files with 1,847 additions and 1,330 deletions.
20 changes: 20 additions & 0 deletions lib/config/recommended.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const rules = require('../recommended-rules');
const util = require('ember-template-imports/src/util');

module.exports = {
root: true,
Expand All @@ -16,4 +17,23 @@ module.exports = {
plugins: ['ember'],

rules,

overrides: [
/**
* We don't want to *always* have the preprocessor active,
* it's only relevant on gjs and gts files.
*
* Additionally, we need to declare a global (which is private API)
* so that ESLint doesn't report errors about the variable being undefined.
* While this is true, it's a temporary thing for babel to do further processing
* on -- and isn't relevant to user-land code.
*/
{
files: ['**/*.gjs', '**/*.gts'],
processor: 'ember/<template>',
globals: {
[util.TEMPLATE_TAG_PLACEHOLDER]: 'readonly',
},
},
],
};
8 changes: 8 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@

const requireIndex = require('requireindex');

const gjs = require('./preprocessors/glimmer');

module.exports = {
rules: requireIndex(`${__dirname}/rules`),
configs: requireIndex(`${__dirname}/config`),
utils: {
ember: require('./utils/ember'),
},
processors: {
// https://eslint.org/docs/developer-guide/working-with-plugins#file-extension-named-processor
'.gjs': gjs,
'.gts': gjs,
'<template>': gjs,
},
};
143 changes: 143 additions & 0 deletions lib/preprocessors/glimmer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
'use strict';

const { getTemplateLocals } = require('@glimmer/syntax');
const {
preprocessEmbeddedTemplates,
} = require('ember-template-imports/lib/preprocess-embedded-templates');
const util = require('ember-template-imports/src/util');

const TRANSFORM_CACHE = new Map();
const TEXT_CACHE = new Map();

/**
* This function is responsible for running the embedded templates transform
* from ember-template-imports.
*
* It takes a gjs or gts file and converts <template> tags to their
* intermediary (private) format.
*
* This is needed so that eslint knows what variables accessed within the template
* are missing or accessed within the JS scope, because
* part of the output includes a snippet that looks like:
*
* `, { strictMode: true, scope: () => ({on,noop}) })]
*
* So if on and noop are not present in the JS, we will have an error to work with
*/
function gjs(text, filename) {
const isGlimmerFile = filename.endsWith('.gjs') || filename.endsWith('.gts');

if (!isGlimmerFile) {
return [
{
filename,
text,
},
];
}

/**
* If the file is already processed, we don't need to process it again.
*
* a file will not have a TEMPLATE_TAG_PLACEHOLDER unless it has been processed
*/
if (text.includes(util.TEMPLATE_TAG_PLACEHOLDER)) {
return [
{
filename,
text,
},
];
}

const preprocessed = preprocessEmbeddedTemplates(text, {
getTemplateLocals,
relativePath: filename,

templateTag: util.TEMPLATE_TAG_NAME,
templateTagReplacement: util.TEMPLATE_TAG_PLACEHOLDER,

includeSourceMaps: false,
includeTemplateTokens: true,
});

// Because the output looks like
// `, { strictMode: true, scope: () => ({on,noop}) })]
// we could accidentally have this line formatted with
// prettier, or trailing commas, or any variety of formatting rules.
//
// We need to disable all of eslint around this "scope" return
// value and allow-list the rules that check for undefined identifiers
const transformed = preprocessed.output;

TRANSFORM_CACHE.set(filename, transformed);
TEXT_CACHE.set(filename, text);

return [
{
filename,
text: transformed,
},
];
}

/**
* This function is for mapping back from the intermediary (private) format
* back to gjs/gts / <template> so that we can appropriately display errors
* or warnings in the correct location in the original source file in the user's
* editor.
*/
function mapRange(messages, filename) {
const transformed = TRANSFORM_CACHE.get(filename);
const original = TEXT_CACHE.get(filename);
const flattened = messages.flat();

if (!transformed) {
return flattened;
}

if (!transformed.includes(util.TEMPLATE_TAG_PLACEHOLDER)) {
return flattened;
}

const lines = transformed.split('\n');
const originalLines = original.split('\n');

return flattened.map((message) => {
const line = lines[message.line - 1];
const token = line.slice(message.column - 1, message.endColumn - 1);

// Now that we have the token, we need to find the location
// in the original text
//
// TODO: Long term, we should use glimmer syntax parsing to find
// the correct node -- otherwise it's too easy to
// partially match on similar text
let originalLineNumber = 0;
let originalColumnNumber = 0;

for (const [index, line] of originalLines.entries()) {
const column = line.search(new RegExp(`\\b${token}\\b`));
if (column > -1) {
originalLineNumber = index + 1;
originalColumnNumber = column + 1;
break;
}
}

const modified = {
...message,
line: originalLineNumber,
column: originalColumnNumber,
endLine: originalLineNumber,
endColumn: originalColumnNumber + token.length,
};

return modified;
});
}

module.exports = {
preprocess: gjs,
postprocess: mapRange,
};
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"jest": {
"coverageThreshold": {
"global": {
"branches": 96,
"branches": 95,
"functions": 99,
"lines": 98,
"statements": 98
Expand All @@ -63,11 +63,14 @@
},
"dependencies": {
"@ember-data/rfc395-data": "^0.0.4",
"@glimmer/syntax": "^0.83.1",
"css-tree": "^2.0.4",
"ember-rfc176-data": "^0.3.15",
"ember-template-imports": "^3.1.1",
"eslint-utils": "^3.0.0",
"estraverse": "^5.2.0",
"lodash.kebabcase": "^4.1.1",
"magic-string": "^0.25.7",
"requireindex": "^1.2.0",
"snake-case": "^3.0.3"
},
Expand Down
Loading

0 comments on commit 9864569

Please sign in to comment.