Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 2 additions & 11 deletions .eslintrc.js → .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
module.exports = {
env: {
browser: true,
commonjs: true,
es2021: true,
},
extends: ['standard', 'plugin:prettier/recommended', 'plugin:n/recommended'],
Expand All @@ -10,24 +9,16 @@ module.exports = {
env: {
node: true,
},
files: ['.eslintrc.{js,cjs}'],
files: ['.eslintrc.cjs', '.prettierrc.cjs'],
parserOptions: {
sourceType: 'script',
},
},
{
env: {
node: true,
},
files: ['tests/**/*.js'],
parserOptions: {
sourceType: 'module',
},
},
],
ignorePatterns: ['tests/fixtures/**/*.js'],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
rules: {},
};
File renamed without changes.
1 change: 1 addition & 0 deletions package.json
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot add publint to lints

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 28bcd98. lint:package (which calls publint) was already wired into pnpm lint via the npm:lint:*(!fix) glob — I updated the script to call publint directly rather than going through pnpm publint.

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
},
"license": "ISC",
"author": "",
"type": "module",
"exports": {
".": "./src/parser/gjs-gts-parser.js",
"./hbs": "./src/parser/hbs-parser.js",
Expand Down
170 changes: 84 additions & 86 deletions src/parser/gjs-gts-parser.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
const tsconfigUtils = require('@typescript-eslint/tsconfig-utils');
const babelParser = require('@babel/eslint-parser/experimental-worker');
const { registerParsedFile } = require('../preprocessor/noop');
const {
patchTs,
replaceExtensions,
syncMtsGtsSourceFiles,
typescriptParser,
} = require('./ts-patch');
const { transformForLint, preprocessGlimmerTemplates, convertAst } = require('./transforms');
import { createRequire } from 'node:module';
import tsconfigUtils from '@typescript-eslint/tsconfig-utils';
import babelParser from '@babel/eslint-parser/experimental-worker';
import { registerParsedFile } from '../preprocessor/noop.js';
import { patchTs, replaceExtensions, syncMtsGtsSourceFiles, typescriptParser } from './ts-patch.js';
import { transformForLint, preprocessGlimmerTemplates, convertAst } from './transforms.js';

const require = createRequire(import.meta.url);

/**
* implements https://eslint.org/docs/latest/extend/custom-parsers
Expand Down Expand Up @@ -131,86 +129,86 @@ function getAllowJs(options) {
/**
* @type {import('eslint').ParserModule}
*/
module.exports = {
meta: {
name: 'ember-eslint-parser',
version: '*',
},

parseForESLint(code, options) {
const allowGjsWasSet = options.allowGjs !== undefined;
const allowGjs = allowGjsWasSet ? options.allowGjs : getAllowJs(options);
let actualAllowGjs;
// Only patch TypeScript if we actually need it.
if (options.programs || options.projectService || options.project) {
({ allowGjs: actualAllowGjs } = patchTs({ allowGjs }));
}
registerParsedFile(options.filePath);
let jsCode = code;
const info = transformForLint(code, options.filePath);
jsCode = info.output;
export const meta = {
name: 'ember-eslint-parser',
version: '*',
};

const isTypescript = options.filePath.endsWith('.gts') || options.filePath.endsWith('.ts');
let useTypescript = true;
export function parseForESLint(code, options) {
const allowGjsWasSet = options.allowGjs !== undefined;
const allowGjs = allowGjsWasSet ? options.allowGjs : getAllowJs(options);
let actualAllowGjs;
// Only patch TypeScript if we actually need it.
if (options.programs || options.projectService || options.project) {
({ allowGjs: actualAllowGjs } = patchTs({ allowGjs }));
}
registerParsedFile(options.filePath);
let jsCode = code;
const info = transformForLint(code, options.filePath);
jsCode = info.output;

if (options.useBabel || !typescriptParser) {
useTypescript = false;
}
const isTypescript = options.filePath.endsWith('.gts') || options.filePath.endsWith('.ts');
let useTypescript = true;

let result = null;
const filePath = options.filePath;
if (options.project || options.projectService) {
jsCode = replaceExtensions(jsCode);
}
if (options.useBabel || !typescriptParser) {
useTypescript = false;
}

if (isTypescript && !typescriptParser) {
throw new Error('Please install typescript to process gts');
}
let result = null;
const filePath = options.filePath;
if (options.project || options.projectService) {
jsCode = replaceExtensions(jsCode);
}

try {
result =
isTypescript || useTypescript
? typescriptParser.parseForESLint(jsCode, {
...options,
ranges: true,
extraFileExtensions: ['.gts', '.gjs'],
filePath,
})
: babelParser.parseForESLint(jsCode, {
...options,
requireConfigFile: false,
ranges: true,
});
if (!info.templateInfos?.length) {
return result;
}
const preprocessedResult = preprocessGlimmerTemplates(info, code);
preprocessedResult.code = code;
const { templateVisitorKeys } = preprocessedResult;
const visitorKeys = { ...result.visitorKeys, ...templateVisitorKeys };
result.isTypescript = isTypescript || useTypescript;
convertAst(result, preprocessedResult, visitorKeys);
if (result.services?.program) {
// Compare allowJs with the actual program's compiler options
const programAllowJs = result.services.program.getCompilerOptions?.()?.allowJs;
if (
!allowGjsWasSet &&
programAllowJs !== undefined &&
actualAllowGjs !== undefined &&
actualAllowGjs !== programAllowJs
) {
// eslint-disable-next-line no-console
console.warn(
'[ember-eslint-parser] allowJs does not match the actual program. Consider setting allowGjs explicitly.\n' +
` Current: ${allowGjs}, Program: ${programAllowJs}`
);
}
syncMtsGtsSourceFiles(result.services.program);
if (isTypescript && !typescriptParser) {
throw new Error('Please install typescript to process gts');
}

try {
result =
isTypescript || useTypescript
? typescriptParser.parseForESLint(jsCode, {
...options,
ranges: true,
extraFileExtensions: ['.gts', '.gjs'],
filePath,
})
: babelParser.parseForESLint(jsCode, {
...options,
requireConfigFile: false,
ranges: true,
});
if (!info.templateInfos?.length) {
return result;
}
const preprocessedResult = preprocessGlimmerTemplates(info, code);
preprocessedResult.code = code;
const { templateVisitorKeys } = preprocessedResult;
const visitorKeys = { ...result.visitorKeys, ...templateVisitorKeys };
result.isTypescript = isTypescript || useTypescript;
convertAst(result, preprocessedResult, visitorKeys);
if (result.services?.program) {
// Compare allowJs with the actual program's compiler options
const programAllowJs = result.services.program.getCompilerOptions?.()?.allowJs;
if (
!allowGjsWasSet &&
programAllowJs !== undefined &&
actualAllowGjs !== undefined &&
actualAllowGjs !== programAllowJs
) {
// eslint-disable-next-line no-console
console.warn(
'[ember-eslint-parser] allowJs does not match the actual program. Consider setting allowGjs explicitly.\n' +
` Current: ${allowGjs}, Program: ${programAllowJs}`
);
}
return { ...result, visitorKeys };
} catch (e) {
console.error(e);
throw e;
syncMtsGtsSourceFiles(result.services.program);
}
},
};
return { ...result, visitorKeys };
} catch (e) {
console.error(e);
throw e;
}
}

export default { meta, parseForESLint };
132 changes: 66 additions & 66 deletions src/parser/hbs-parser.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const eslintScope = require('eslint-scope');
const DocumentLines = require('../utils/document');
const { processGlimmerTemplate, buildGlimmerVisitorKeys } = require('./transforms');
import * as eslintScope from 'eslint-scope';
import DocumentLines from '../utils/document.js';
import { processGlimmerTemplate, buildGlimmerVisitorKeys } from './transforms.js';

// Constant: Program + all Glimmer node types. Computed once at module load.
const hbsVisitorKeys = { Program: ['body'], ...buildGlimmerVisitorKeys() };
Expand All @@ -17,76 +17,76 @@ const hbsVisitorKeys = { Program: ['body'], ...buildGlimmerVisitorKeys() };
/**
* @type {import('eslint').ParserModule}
*/
module.exports = {
meta: {
name: 'ember-eslint-parser/hbs',
version: '*',
},
export const meta = {
name: 'ember-eslint-parser/hbs',
version: '*',
};

parseForESLint(code, options) {
const filePath = (options && options.filePath) || '<hbs>';
const codeLines = new DocumentLines(code);
export function parseForESLint(code, options) {
const filePath = (options && options.filePath) || '<hbs>';
const codeLines = new DocumentLines(code);

let result;
try {
result = processGlimmerTemplate({
templateContent: code,
codeLines,
templateRange: [0, code.length],
let result;
try {
result = processGlimmerTemplate({
templateContent: code,
codeLines,
templateRange: [0, code.length],
});
} catch (e) {
// Transform glimmer parse error to ESLint-compatible error
const loc = e.location || (e.hash && e.hash.loc);
if (loc && loc.start) {
const err = Object.assign(new SyntaxError(e.message), {
lineNumber: loc.start.line,
column: loc.start.column,
index: codeLines.positionToOffset(loc.start),
fileName: filePath,
});
} catch (e) {
// Transform glimmer parse error to ESLint-compatible error
const loc = e.location || (e.hash && e.hash.loc);
if (loc && loc.start) {
const err = Object.assign(new SyntaxError(e.message), {
lineNumber: loc.start.line,
column: loc.start.column,
index: codeLines.positionToOffset(loc.start),
fileName: filePath,
});
throw err;
}
throw e;
throw err;
}
throw e;
}

const { ast: templateNode, comments } = result;

// Wrap in a synthetic Program node (required by ESLint)
const program = {
type: 'Program',
body: [templateNode],
tokens: templateNode.tokens,
comments,
range: [0, code.length],
start: 0,
end: code.length,
loc: {
start: { line: 1, column: 0 },
end: codeLines.offsetToPosition(code.length),
},
};

const { ast: templateNode, comments } = result;
// Build visitor keys: Program + all Glimmer node types
const visitorKeys = hbsVisitorKeys;

// Wrap in a synthetic Program node (required by ESLint)
const program = {
// Create an empty scope manager.
// For HBS, all locals are assumed to be defined at runtime,
// so we don't track variable references (no no-undef errors).
const scopeManager = eslintScope.analyze(
{
type: 'Program',
body: [templateNode],
tokens: templateNode.tokens,
comments,
body: [],
range: [0, code.length],
start: 0,
end: code.length,
loc: {
start: { line: 1, column: 0 },
end: codeLines.offsetToPosition(code.length),
},
};
loc: program.loc,
},
{ range: true }
);

// Build visitor keys: Program + all Glimmer node types
const visitorKeys = hbsVisitorKeys;
return {
ast: program,
scopeManager,
visitorKeys,
services: {},
};
}

// Create an empty scope manager.
// For HBS, all locals are assumed to be defined at runtime,
// so we don't track variable references (no no-undef errors).
const scopeManager = eslintScope.analyze(
{
type: 'Program',
body: [],
range: [0, code.length],
loc: program.loc,
},
{ range: true }
);

return {
ast: program,
scopeManager,
visitorKeys,
services: {},
};
},
};
export default { meta, parseForESLint };
Loading