Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert to ESM #2262

Merged
merged 62 commits into from
Dec 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
dca354e
breaking: convert to esm
bmish Dec 4, 2021
c8a1da5
Merge branch 'next' into esm
bmish Dec 16, 2021
a6ac218
wip import plugin/config
bmish Dec 17, 2021
e941271
fix async config loading in Linter class
bmish Dec 20, 2021
b380487
fixtures import instead of require rule
bmish Dec 20, 2021
1e2712a
wip cjs fixture files
bmish Dec 20, 2021
52a2c39
fix plugin resolving for other file extensions
bmish Dec 20, 2021
5b737e4
fix html tests and lint violations
bmish Dec 20, 2021
65b3856
fix print config test
bmish Dec 20, 2021
052aad0
fix shebang test
bmish Dec 20, 2021
2af478f
fix formatting in indices
bmish Dec 20, 2021
662f026
fix lint
bmish Dec 20, 2021
87659bb
use ESM for scripts
bmish Dec 20, 2021
b343eb3
test coverage
bmish Dec 20, 2021
aa0a1b4
export NODE_OPTIONS for windows
bmish Dec 20, 2021
ac11ee6
fix NODE_OPTIONS for windows again
bmish Dec 20, 2021
8a7082e
consistent default export
bmish Dec 20, 2021
2915d00
remove incorrect await
bmish Dec 20, 2021
af34b38
add async function to rejects expects
bmish Dec 20, 2021
0b679a6
tweaks
bmish Dec 20, 2021
ab3e2e4
add comments
bmish Dec 21, 2021
fa1ccee
add mjs extension config/plugin test
bmish Dec 21, 2021
65ab235
dev script to esm
bmish Dec 21, 2021
2e6e880
improve comments
bmish Dec 21, 2021
3ef3381
improve comments
bmish Dec 21, 2021
cbf9079
fix mjs fixture test
bmish Dec 21, 2021
8d892af
use separate helper file instead of exporting functions from CLI bina…
bmish Dec 22, 2021
e92578f
add await to expect rejects tests
bmish Dec 22, 2021
5de4747
test jest runInBand
bmish Dec 22, 2021
fbad6c9
Revert "test jest runInBand"
bmish Dec 22, 2021
ed1b32f
remove unnecessary CJS fallback for plugin/config loading
bmish Dec 22, 2021
8a44de4
jest useFakeTimers
bmish Dec 22, 2021
14fe0ca
use fake timers except for long-running rule-test.js
bmish Dec 22, 2021
f2ed7bb
Revert "remove unnecessary CJS fallback for plugin/config loading"
bmish Dec 22, 2021
2f6b7ee
Revert "use fake timers except for long-running rule-test.js"
bmish Dec 22, 2021
4fec95b
Revert "jest useFakeTimers"
bmish Dec 22, 2021
9419b21
remove yarn --ignore-engines
bmish Dec 22, 2021
f33308a
switch volta-cli to actions/setup-node for CI
bmish Dec 22, 2021
ef5befc
regenerate yarn.lock
bmish Dec 22, 2021
963257f
jest maxWorkers 150
bmish Dec 22, 2021
5948f73
Revert "jest maxWorkers 150"
bmish Dec 22, 2021
d5cc369
jest debug detectOpenHandles
bmish Dec 22, 2021
08b44c3
jest maxConcurrency 1
bmish Dec 22, 2021
71b3b46
Revert "jest maxConcurrency 1"
bmish Dec 22, 2021
22637bc
jest no transform
bmish Dec 22, 2021
b7daa1f
add babel for jest
bmish Dec 22, 2021
1e27c8e
Revert "jest no transform"
bmish Dec 22, 2021
f2a5ddc
jest verbose
bmish Dec 22, 2021
f3c2f19
Revert "add babel for jest"
bmish Dec 22, 2021
595ad6d
Revert "jest verbose"
bmish Dec 22, 2021
6b7ba73
Revert "jest debug detectOpenHandles"
bmish Dec 22, 2021
30103e4
disable broken test file in older versions of Node
bmish Dec 22, 2021
b629856
Revert "regenerate yarn.lock"
bmish Dec 22, 2021
5d20713
Revert "switch volta-cli to actions/setup-node for CI"
bmish Dec 22, 2021
d89d10c
improve comments
bmish Dec 22, 2021
d4917c5
fix URL path for windows
bmish Dec 22, 2021
8c533e9
unicorn/prefer-module lint rule already enabled so remove redundant line
bmish Dec 22, 2021
8da9528
improve comments
bmish Dec 23, 2021
42c1591
raise test coverage percentages back up
bmish Dec 23, 2021
5e21d7c
fix test coverage percentage
bmish Dec 23, 2021
3b295e0
fix comment
bmish Dec 23, 2021
7b868ba
Merge branch 'next' into esm
bmish Dec 23, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
23 changes: 19 additions & 4 deletions .eslintrc.js → .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
module.exports = {
parser: '@babel/eslint-parser',
Copy link
Member Author

Choose a reason for hiding this comment

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

@babel/eslint-parser added for parsing top-level await syntax now allowed in ESM.

parserOptions: {
ecmaVersion: 2021,
sourceType: 'script',
sourceType: 'module',
requireConfigFile: false,
},
env: {
node: true,
Expand Down Expand Up @@ -82,7 +84,7 @@ module.exports = {
'filenames/match-regex': ['error', '^.?[a-z0-9-]+$'], // Kebab-case.

// Optional import rules:
'import/extensions': 'error',
'import/extensions': ['error', 'always'],
Copy link
Member Author

Choose a reason for hiding this comment

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

This enforces that we include file extensions in all import statements.

'import/first': 'error',
'import/newline-after-import': 'error',
'import/no-absolute-path': 'error',
Expand Down Expand Up @@ -111,6 +113,14 @@ module.exports = {
},
],

// Node rules:
'node/no-unsupported-features/es-syntax': [
'error',
{
ignores: ['dynamicImport', 'modules'], // False positives: https://github.com/mysticatea/eslint-plugin-node/issues/250
},
],

// Unicorn rules:
'unicorn/consistent-destructuring': 'off',
'unicorn/consistent-function-scoping': 'off',
Expand All @@ -120,14 +130,19 @@ module.exports = {
'unicorn/no-lonely-if': 'off',
'unicorn/no-null': 'off',
'unicorn/no-useless-undefined': 'off',
'unicorn/prefer-module': 'off',
'unicorn/prefer-ternary': 'off',
'unicorn/prevent-abbreviations': 'off',
},

overrides: [
{
files: ['bin/**/*.js'],
files: ['**/*.cjs'],
parserOptions: {
sourceType: 'script',
},
},
{
files: ['bin/**/*.js', 'lib/helpers/cli.js'],
rules: {
'no-console': 'off',
},
Expand Down
10 changes: 7 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:

- run: yarn test:jest

- run: yarn update:readme && git diff --exit-code
- run: yarn update && git diff --exit-code

nodeX:
name: Node ${{ matrix.node-version }} - ${{ matrix.os }}
Expand All @@ -53,9 +53,13 @@ jobs:
node-version: ${{ matrix.node-version }}

- name: install dependencies
run: yarn install --frozen-lockfile --ignore-engines
run: yarn install --frozen-lockfile

- run: yarn test:jest
# Temporarily exclude `test/unit/get-config-test.js` in Node versions before 16 (TODO: remove eventually).
# Tests triggering `resolveProjectConfig` and thus `await import()` for config/plugin loading cause this error:
# ReferenceError: You are trying to `import` a file after the Jest environment has been torn down.
# Possibly related: https://github.com/facebook/jest/issues/11438
- run: yarn test:jest --testPathIgnorePatterns="test/unit/get-config-test.js"

floating-dependencies:
name: Floating Dependencies
Expand Down
File renamed without changes.
274 changes: 16 additions & 258 deletions bin/ember-template-lint.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
#!/usr/bin/env node

'use strict';
/* eslint node/shebang:"off" -- shebang needed so this script can be run directly */

// Use V8's code cache to speed up instantiation time:
require('v8-compile-cache'); // eslint-disable-line import/no-unassigned-import

const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
import 'v8-compile-cache'; // eslint-disable-line import/no-unassigned-import

const {
import {
compactTodoStorageFile,
getTodoStorageFilePath,
getTodoConfig,
validateConfig,
} = require('@lint-todo/utils');
const ci = require('ci-info');
const getStdin = require('get-stdin');
const globby = require('globby');
const isGlob = require('is-glob');
const micromatch = require('micromatch');

const Linter = require('../lib');
const camelize = require('../lib/helpers/camelize');
const processResults = require('../lib/helpers/process-results');
} from '@lint-todo/utils';
import getStdin from 'get-stdin';
import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import { promisify } from 'node:util';

import Printer from '../lib/formatters/default.js';
import { parseArgv, getFilesToLint } from '../lib/helpers/cli.js';
import processResults from '../lib/helpers/process-results.js';
import Linter from '../lib/linter.js';

const readFile = promisify(fs.readFile);

Expand Down Expand Up @@ -55,233 +51,6 @@ async function buildLinterOptions(workingDir, filePath, filename = '', isReading
}
}

function executeGlobby(workingDir, pattern, ignore) {
let supportedExtensions = new Set(['.hbs', '.handlebars']);

// `--no-ignore-pattern` results in `ignorePattern === [false]`
let options =
ignore[0] === false ? { cwd: workingDir } : { cwd: workingDir, gitignore: true, ignore };

return globby
.sync(pattern, options)
.filter((filePath) => supportedExtensions.has(path.extname(filePath)));
}

function isFile(possibleFile) {
try {
let stat = fs.statSync(possibleFile);
return stat.isFile();
} catch {
return false;
}
}

function expandFileGlobs(workingDir, filePatterns, ignorePattern, glob = executeGlobby) {
let result = new Set();

for (const pattern of filePatterns) {
let isLiteralPath = !isGlob(pattern) && isFile(path.resolve(workingDir, pattern));

if (isLiteralPath) {
let isIgnored = micromatch.isMatch(pattern, ignorePattern);

if (!isIgnored) {
result.add(pattern);
}

continue;
}

const globResults = glob(workingDir, pattern, ignorePattern);
if (!globResults || globResults.length === 0) {
throw new Error(`No files matching the pattern were found: "${pattern}"`);
}

for (const filePath of globResults) {
result.add(filePath);
}
}

return result;
}

function getFilesToLint(workingDir, filePatterns, ignorePattern = []) {
let files;

if (filePatterns.length === 0 || filePatterns.includes('-') || filePatterns.includes(STDIN)) {
files = new Set([STDIN]);
} else {
files = expandFileGlobs(workingDir, filePatterns, ignorePattern);
}

return files;
}

/**
* @param {Object} specifiedOptions - options passed to yargs (option names should be in dasherized format)
* @returns {String[]} a list of all possible CLI option names
*/
function getPossibleOptionNames(specifiedOptions) {
const optionAliases = Object.values(specifiedOptions)
.map((option) => option.alias)
.filter((option) => option !== undefined);
const dasherizedOptionNames = [...Object.keys(specifiedOptions), ...optionAliases];
const camelizedOptionNames = dasherizedOptionNames.map((name) => camelize(name));
const negatedDasherizedOptionNames = dasherizedOptionNames.map((name) =>
name.startsWith('no-') ? name.slice(3) : `no-${name}`
);
const negatedCamelizedOptionNames = negatedDasherizedOptionNames.map((name) => camelize(name));
return [
...dasherizedOptionNames,
...camelizedOptionNames,
// Since yargs `boolean-negation` option is enabled (by default), assume any option can be passed with `no`/negated prefix.
...negatedDasherizedOptionNames,
...negatedCamelizedOptionNames,
];
}

function parseArgv(_argv) {
const specifiedOptions = {
'config-path': {
describe: 'Define a custom config path',
default: '.template-lintrc.js',
type: 'string',
},
config: {
describe:
'Define a custom configuration to be used - (e.g. \'{ "rules": { "no-implicit-this": "error" } }\') ',
type: 'string',
},
quiet: {
describe: 'Ignore warnings and only show errors',
boolean: true,
},
rule: {
describe:
'Specify a rule and its severity to add that rule to loaded rules - (e.g. `no-implicit-this:error` or `rule:["error", { "allow": ["some-helper"] }]`)',
type: 'string',
},
filename: {
describe: 'Used to indicate the filename to be assumed for contents from STDIN',
type: 'string',
},
fix: {
describe: 'Fix any errors that are reported as fixable',
boolean: true,
default: false,
},
format: {
describe: 'Specify format to be used in printing output',
type: 'string',
default: 'pretty',
},
'output-file': {
describe: 'Specify file to write report to',
type: 'string',
implies: 'format',
},
verbose: {
describe: 'Output errors with source description',
boolean: true,
},
'working-directory': {
alias: 'cwd',
describe: 'Path to a directory that should be considered as the current working directory.',
type: 'string',
// defaulting to `.` here to refer to `process.cwd()`, setting the default to `process.cwd()` itself
// would make our snapshots unstable (and make the help output unaligned since most directory paths
// are fairly deep)
default: '.',
},
'no-config-path': {
describe: 'Does not use the local template-lintrc, will use a blank template-lintrc instead',
boolean: true,
},
'update-todo': {
describe: 'Update list of linting todos by transforming lint errors to todos',
default: false,
boolean: true,
},
'include-todo': {
describe: 'Include todos in the results',
default: false,
boolean: true,
},
'clean-todo': {
describe: 'Remove expired and invalid todo files',
default: !ci.isCI,
boolean: true,
},
'compact-todo': {
describe: 'Compacts the .lint-todo storage file, removing extraneous todos',
boolean: true,
},
'todo-days-to-warn': {
describe: 'Number of days after its creation date that a todo transitions into a warning',
type: 'number',
},
'todo-days-to-error': {
describe: 'Number of days after its creation date that a todo transitions into an error',
type: 'number',
},
'ignore-pattern': {
describe: 'Specify custom ignore pattern (can be disabled with --no-ignore-pattern)',
type: 'array',
default: ['**/dist/**', '**/tmp/**', '**/node_modules/**'],
},
'no-inline-config': {
describe: 'Prevent inline configuration comments from changing config or rules',
boolean: true,
},
'print-config': {
describe: 'Print the configuration for the given file',
default: false,
boolean: true,
},
'max-warnings': {
describe: 'Number of warnings to trigger nonzero exit code',
type: 'number',
},
};

let parser = require('yargs')
.scriptName('ember-template-lint')
.usage('$0 [options] [files..]')
.options(specifiedOptions)
.help()
.version();

parser.parserConfiguration({
'greedy-arrays': false,
});

if (_argv.length === 0) {
parser.showHelp();
parser.exit(1);
} else {
let options = parser.parse(_argv);

// TODO: Eventually use yargs strict() or strictOptions() to disallow unknown options (blocked by some inconsistencies in how we tell yargs about our options).
const possibleOptionNames = getPossibleOptionNames(specifiedOptions);
for (const optionName of Object.keys(options)) {
if (
!['$0', '_'].includes(optionName) && // Built-in yargs options.
!possibleOptionNames.includes(optionName)
) {
console.error(`Unknown option: --${optionName}`);
parser.exit(1);
return;
}
}

if (options.workingDirectory === '.') {
options.workingDirectory = process.cwd();
}

return options;
}
}

function getTodoConfigFromCommandLineOptions(options) {
let todoConfig = {};

Expand Down Expand Up @@ -425,7 +194,7 @@ async function run() {
let fileResults;

if (options.printConfig) {
let fileConfig = linter.getConfigForFile(linterOptions);
let fileConfig = await linter.getConfigForFile(linterOptions);

_console.log(JSON.stringify(fileConfig, null, 2));
process.exitCode = 0;
Expand Down Expand Up @@ -481,22 +250,11 @@ async function run() {
let hasTodos = options.includeTodo && results.todoCount;
let hasUpdatedTodos = options.updateTodo;

let Printer = require('../lib/formatters/default');
Copy link
Member Author

Choose a reason for hiding this comment

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

This is now a static import at top of file. Could possibly be moved back to dynamic await import() but static import seems fine.

Copy link
Member

Choose a reason for hiding this comment

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

I think this is fine since we're always loading a printer.

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay, I did this in a few places since static imports don't require switching stuff to await, but probably wouldn't be difficult to move it back to dynamic (using await import()) later if needed.

let printer = new Printer({
...options,
hasResultData: hasErrors || hasWarnings || hasTodos || hasUpdatedTodos,
});
printer.print(results, todoInfo);
}

// exports are for easier unit testing
module.exports = {
_parseArgv: parseArgv,
_expandFileGlobs: expandFileGlobs,
_getFilesToLint: getFilesToLint,
_getPossibleOptionNames: getPossibleOptionNames,
};

if (require.main === module) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Moved the exported helper functions from this file to a new helper file lib/helpers/cli.js since it's tricky in ESM to have a binary script that can also be imported.

run();
}
run();