Skip to content

Support programmatic linting, remove FS-centric bundle management #59

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

Merged
merged 13 commits into from
May 30, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"node": true
},
"parserOptions": {
"ecmaVersion": 6
"ecmaVersion": "latest"
},
"rules": {
"strict": ["error", "global"]
Expand Down
3 changes: 3 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ module.exports = {
testMatch: [
'<rootDir>/test/plugin.js',
'<rootDir>/test/lib/rules/**/*.js',
'<rootDir>/test/lib/util/**/*.js',
'<rootDir>/test/lib/lwc-bundle.js',
'<rootDir>/test/lib/processor.js',
'!**/test/lib/rules/**/test.js',
'!**/test/lib/rules/artifacts-combined-files/helper.js',
'!**/test/lib/rules/shared.js'
Expand Down
11 changes: 11 additions & 0 deletions lib/configs/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@
module.exports = {
plugins: ['@salesforce/lwc-graph-analyzer'],
processor: '@salesforce/lwc-graph-analyzer/bundleAnalyzer',
parser: '@babel/eslint-parser',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without this, every project that included our plugin also had to include one of the LWC ESLint configs from eslint-config-lwc, to get the parser that would accept LWC code with decorators.

parserOptions: {
ecmaVersion: 'latest',
requireConfigFile: false,
sourceType: 'module',
babelOptions: {
parserOpts: {
plugins: [['decorators', { decoratorsBeforeExport: false }]]
}
}
},
overrides: [
{
files: ['**/*.html']
Expand Down
150 changes: 150 additions & 0 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import type { Linter, ESLint } from 'eslint';

/**
* Represents a file in an LWC bundle, with its content and designation as the primary file,
* i.e. the file requested by ESLint to be linted.
*/
export type LwcBundleFile = {
/** The name of the file */
filename: string;
/** The content of the file */
content: string;
/** Whether this is the primary file being linted by ESLint */
isPrimary: boolean;
};

/**
* Represents a bundle of files that make up an LWC component. The bundle contains the JavaScript
* file and any HTML template files that comprise the component. One file in the bundle will be
* designated as the primary file, which is the file that ESLint is currently processing.
*/
declare class LwcBundle {
/**
* Creates a new LWC bundle instance
*
* @param componentBaseName - The base name of the component (e.g., 'myComponent' for 'myComponent.js' et al)
* @param js - The JavaScript file of the bundle, if present
* @param htmlTemplates - The HTML template files of the bundle, if present
*/
constructor(componentBaseName: string, js?: LwcBundleFile, htmlTemplates?: LwcBundleFile[]);

/** Gets the base name of the component */
get componentBaseName(): string;

/** Gets the JavaScript file of the bundle, if present */
get js(): LwcBundleFile | undefined;

/** Gets the HTML template files of the bundle, if present */
get htmlTemplates(): LwcBundleFile[] | undefined;

/** Gets the primary file being linted by ESLint */
get primaryFile(): LwcBundleFile;

/**
* Creates a record of filenames to their content for Komaci analysis
*
* @returns A record mapping filenames to their content
*/
filesRecord(): Record<string, string>;

/**
* Sets the primary file in the bundle based on content matching
*
* @param content - The content to match against
* @returns True if a matching file was found and set as primary
*/
setPrimaryFileByContent(content: string): boolean;

/**
* Creates an `LwcBundle` instance from the specified content files. The primary file
* designation will be set later when the bundle is processed by ESLint.
*
* @param componentBaseName - The base name of the component
* @param jsContent - The content of the JavaScript file, if present
* @param htmlTemplateContent - The contents of the HTML template files, if any
* @returns A new LWC bundle instance
*/
static lwcBundleFromContent(
componentBaseName: string,
jsContent?: string,
...htmlTemplateContent: string[]
): LwcBundle;

/**
* Creates a bundle from filesystem files
*
* @param lwcFileContent - The content of the file being processed
* @param lwcFilePath - The path to the file being processed
* @param lwcFileExtension - The file extension of the file being processed
* @returns A new LWC bundle instance, or null if the file cannot be found
*/
static lwcBundleFromFilesystem(
lwcFileContent: string,
lwcFilePath: string,
lwcFileExtension: string
): LwcBundle | null;
}

/**
* ESLint processor that analyzes LWC bundles. This will set up the LWC bundle to be processed
* by Komaci.
*/
export class BundleAnalyzer implements Linter.Processor {
/** Gets the current LWC bundle being processed */
get lwcBundle(): LwcBundle | null;

/** Sets the LWC bundle to be processed */
set lwcBundle(value: LwcBundle | null);

/**
* Sets the LWC bundle content from content files
*
* @param componentBaseName - The base name of the component
* @param jsContent - The content of the JavaScript file, if present
* @param htmlTemplateContent - The contents of the HTML template files, if any
*/
setLwcBundleFromContent(
componentBaseName: string,
jsContent?: string,
...htmlTemplateContent: string[]
): void;

/**
* Preprocesses the input file for ESLint
*
* @param text - The content of the ESLint file
* @param filename - The input filename
* @returns An array of files and their content to be processed
*/
preprocess(text: string, filename: string): string[];

/**
* Postprocesses the linting results
*
* @param messages - The two-dimensional array of messages returned from linting
* @param filename - The input filename
* @returns A one-dimensional flattened array of messages
*/
postprocess(messages: Linter.LintMessage[][], filename: string): Linter.LintMessage[];

/** Whether the processor supports autofix */
supportsAutofix: boolean;
}

/**
* The ESLint plugin for LWC graph analysis
*/
declare const lwcGraphAnalyzerPlugin: ESLint.Plugin & {
/** The bundle analyzer processor */
processors: {
bundleAnalyzer: BundleAnalyzer;
};
/** The plugin's configuration presets */
configs: {
base: Linter.Config;
recommended: Linter.Config;
};
};

export = lwcGraphAnalyzerPlugin;
export { LwcBundle };
6 changes: 4 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@

'use strict';

const processor = require('./processor');
const bundleAnalyzer = require('./processor');
const base = require('./configs/base');
const recommended = require('./configs/recommended');
const LwcBundle = require('./lwc-bundle');

const noGetterContainsMoreThanReturnStatement = require('./rules/no-getter-contains-more-than-return-statement');
const noAssignmentExpressionAssignsValueToMemberVariable = require('./rules/no-assignment-expression-assigns-value-to-member-variable');
Expand Down Expand Up @@ -117,6 +118,7 @@ module.exports = {
recommended
},
processors: {
bundleAnalyzer: processor
bundleAnalyzer
}
};
module.exports.LwcBundle = LwcBundle;
Loading