How TSSLint Handles Virtual TypeScript Files for Vue, MDX, and Astro
TSSLint treats Vue, MDX, and Astro files as virtual TypeScript files by loading framework-specific language plugins from @volar/language-hub, decorating the TypeScript service host to expose virtual file names, and mapping diagnostics back to the original source files.
TSSLint provides first-class linting support for modern meta-frameworks by converting non-TypeScript source files into virtual TypeScript representations. This architecture enables type-aware lint rules to run against Vue Single File Components (SFCs), MDX documents, and Astro templates as if they were standard .ts files.
Language Plugin Discovery and Loading
The CLI entry point orchestrates framework support through the load() function in packages/cli/lib/languagePlugins.ts. This module dynamically instantiates language plugins based on command-line flags.
CLI Flags for Meta-Frameworks
TSSLint exposes dedicated flags for each supported framework:
--vue-project– Enables Vue SFC support viacreateVuePlugin--vue-vine-project– Enables Vue Vine support viacreateVueVinePlugins--mdx-project– Enables MDX support viacreateMdxPlugin--astro-project– Enables Astro support viacreateAstroPlugin
Volar Language Hub Integration
Each flag triggers the corresponding factory function from @volar/language-hub. For example, when --vue-project is specified, the CLI executes:
createVuePlugin(ts, tsconfig)
These plugins implement the Volar language plugin API, which defines how to parse framework-specific syntax and generate virtual TypeScript files containing the extracted script content plus type declarations for template bindings.
Decorating the TypeScript Service Host
Once plugins are loaded, TSSLint integrates them into the TypeScript language service through host decoration in packages/cli/lib/worker.ts.
Virtual File Name Exposure
At line 189 in worker.ts, the CLI calls:
decorateLanguageServiceHost(ts, language, linterHost)
This function from @volar/typescript augments the LanguageServiceHost with three critical capabilities:
- Virtual file enumeration – Exposes generated
.tsfile names for each.vue,.mdx, or.astrosource - Snapshot provision – Implements
getScriptSnapshotto returnts.IScriptSnapshotinstances containing the virtual TypeScript content - Extension registration – Adds framework extensions to
typescript.extraFileExtensions
Enabling Non-TypeScript Extensions
To prevent the TypeScript compiler from rejecting non-standard extensions, worker.ts lines 103-108 check if any plugin contributes to extraFileExtensions. If so, the CLI sets:
allowNonTsExtensions: true
This configuration allows the language service to process .vue, .mdx, and .astro files without throwing extension-related errors.
Linting Virtual Files with the Core Engine
The core linting engine in packages/core/index.ts operates transparently on both physical and virtual files. After host decoration, the linter is instantiated at lines 58-74:
linter = core.createLinter(
{ languageService: linterLanguageService,
languageServiceHost: linterHost,
typescript: ts },
path.dirname(configFile),
config,
() => [],
linterSyntaxOnlyLanguageService,
);
The languageService now recognizes virtual TypeScript files as standard source files. When linter.lint(fileName, cache) executes, it processes the virtual .ts content generated from Vue templates, MDX components, or Astro frontmatter, enabling type-aware rules to validate framework-specific syntax.
Mapping Diagnostics to Source Files
When diagnostics originate from virtual files, TSSLint ensures users see errors mapped to their original source locations. In packages/core/index.ts lines 190-227, the createRelatedInformation function rewrites the file property of diagnostics:
// Simplified representation of the mapping logic
if (diagnostic.file && isVirtualFile(diagnostic.file.fileName)) {
const sourceFile = getSourceFile(diagnostic.file.fileName);
diagnostic.file = sourceFile;
}
This transformation ensures that error reports point to .vue, .mdx, or .astro files rather than internal virtual .ts shadows, providing a seamless developer experience.
Configuration and Usage Example
To lint Vue SFCs, MDX documents, or Astro templates, specify the framework flag when running the CLI:
# Lint Vue files
tsslint --vue-project ./tsconfig.json
# Lint MDX files
tsslint --mdx-project ./tsconfig.json
# Lint Astro files
tsslint --astro-project ./tsconfig.json
Configure rule inclusion in tsslint.config.ts:
// tsslint.config.ts
export default {
include: ['**/*.vue', '**/*.mdx', '**/*.astro', '**/*.ts'],
rules: {
'no-console': 'error',
'no-unused-vars': 'error',
},
};
Summary
- TSSLint handles Vue, MDX, and Astro files as virtual TypeScript files through the Volar language plugin system.
- Framework plugins are loaded dynamically via
packages/cli/lib/languagePlugins.tsbased on CLI flags like--vue-projectand--mdx-project. - The TypeScript service host is decorated in
packages/cli/lib/worker.tsto expose virtual file names and enable non-TypeScript extensions. - The core linter operates transparently on virtual files in
packages/core/index.ts, treating generated TypeScript from framework files as standard source. - Diagnostics are mapped back to original files so developers see errors in their
.vue,.mdx, or.astrosources rather than virtual shadows.
Frequently Asked Questions
How does TSSLint differ from ESLint when linting Vue files?
TSSLint uses the TypeScript language service to create virtual TypeScript representations of Vue SFCs, enabling type-aware linting against actual TypeScript types extracted from templates. ESLint typically uses parsers like vue-eslint-parser to generate an AST, but does not inherently provide TypeScript type information for template bindings without additional configuration.
Can I use TSSLint with multiple meta-frameworks in the same project?
Yes. You can specify multiple framework flags simultaneously when running the CLI, such as tsslint --vue-project --mdx-project ./tsconfig.json. The load() function in packages/cli/lib/languagePlugins.ts aggregates all requested plugins into the languages array, and the decorated language service host handles virtual files from all frameworks concurrently.
What happens if a virtual file contains TypeScript errors in the template section?
When a Vue template contains type errors, the createVuePlugin generates a virtual TypeScript file that includes type declarations for template bindings. If these declarations conflict with actual types, the TypeScript language service reports diagnostics. The core linter in packages/core/index.ts then maps these diagnostics back to the original .vue file using the createRelatedInformation logic, displaying the error at the correct line in the template section.
Does TSSLint support custom language plugins beyond Vue, MDX, and Astro?
Yes. While TSSLint provides built-in CLI flags for Vue, Vue-Vine, MDX, and Astro, the architecture in packages/cli/lib/languagePlugins.ts is designed to accept any plugin conforming to the Volar language plugin API. You can theoretically pass custom language plugin factories to the load() function, provided they implement the required methods for generating virtual TypeScript files and providing script snapshots.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →