- 
          
 - 
                Notifications
    
You must be signed in to change notification settings  - Fork 32
 
feat: implement tsconfig include/exclude/files matching #790
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
base: main
Are you sure you want to change the base?
Conversation
          How to use the Graphite Merge QueueAdd the label merge to this PR to add it to the merge queue. You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.  | 
    
64b5156    to
    5465eed      
    Compare
  
    
          Codecov Report❌ Patch coverage is  
 Additional details and impacted files@@            Coverage Diff             @@
##             main     #790      +/-   ##
==========================================
+ Coverage   93.59%   93.77%   +0.17%     
==========================================
  Files          17       18       +1     
  Lines        3093     3262     +169     
==========================================
+ Hits         2895     3059     +164     
- Misses        198      203       +5     ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
  | 
    
          
CodSpeed Performance ReportMerging #790 will not alter performanceComparing  Summary
 Footnotes
  | 
    
Add comprehensive test suite for TypeScript's include, exclude, and files fields from tsconfig.json. Tests are ported from vite-tsconfig-paths. This is part of implementing issue #764 to support include/exclude/files for Vite/Rolldown integration. Test Coverage: - Basic include pattern matching (src/**/*.ts) - Exclude pattern filtering (**/*.test.ts) - Files field priority over exclude - Default include/exclude values - ${configDir} template variable substitution - Extends inheritance behavior - Empty files/include arrays - Globstar (**) and wildcard (*) patterns - Character sets ([A-Z]*.ts) - Monorepo patterns (packages/*/src/**) - Project references with include/exclude - Paths outside project root - outDir auto-exclusion - Complex multi-glob patterns - Case sensitivity handling Fixtures: - 19 test fixture directories with tsconfig.json and source files - Covers real-world patterns from vite-tsconfig-paths Tests currently fail as expected (TDD approach) - implementation follows. Related: #764 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Refactors tests to use full resolution flow and implements actual include/exclude filtering during module resolution. ## Changes ### Core Implementation (src/lib.rs) - Add include/exclude check in `load_tsconfig_paths()` before applying path mappings - Only check actual files, not directories (to support directory-based resolution) - If importer doesn't match tsconfig patterns, skip that tsconfig's path mappings ### File Matcher Creation (src/tsconfig/mod.rs) - Ensure file_matcher is always created for non-empty tsconfigs - Defaults to include: ["**/*"] when no patterns specified - All tsconfigs now have a file_matcher (except explicit empty case) ### Test Strategy (src/tests/tsconfig_include_exclude.rs) - Refactor from testing `matches_file()` directly to testing full `resolver.resolve()` - Verify path aliases work when importer is included - Verify path aliases don't work when importer is excluded - Test cases cover: include patterns, exclude patterns, default behavior ### Fixtures - Add imports to test fixtures (index.ts files) - Add path aliases (baseUrl/paths) to tsconfig.json files - Create helper.ts for exclude_basic fixture ## Testing All 158 tests pass, including new resolution-based include/exclude tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
7166911    to
    2a6e0a9      
    Compare
  
    Add comprehensive unit tests that directly test pattern matching logic
in isolation, using the previously unused fixtures.
## Changes
- Create `src/tests/tsconfig_file_matcher.rs` with 10 unit tests
- Each test covers a specific pattern type:
  - `test_globstar_patterns` - Tests **/*.ts (matches at any depth)
  - `test_wildcard_patterns` - Tests src/*.ts (single level)
  - `test_character_set_patterns` - Tests [A-Z]*.ts (character ranges)
  - `test_complex_patterns` - Tests src/**/test/**/*.spec.ts (nested)
  - `test_monorepo_patterns` - Tests packages/*/src/**/* (monorepo)
  - `test_files_priority` - Tests files array overrides exclude
  - `test_outdir_exclude` - Tests automatic outDir exclusion
  - `test_absolute_patterns` - Tests absolute path handling
  - `test_configdir_syntax` - Tests ${configDir} template variable
  - `test_without_baseurl` - Tests default include/exclude
- Helper function `create_matcher_from_fixture()` reads tsconfig.json
  and creates TsconfigFileMatcher with template substitution
- All 10 fixtures that were created but unused are now tested
- Total: 168 tests passing (10 new unit tests added)
## Benefits
- Tests pattern matching in isolation (faster than integration tests)
- Clear documentation of all supported pattern types
- Better coverage of edge cases
- Reuses existing fixture infrastructure
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
    | // 1. Check files array first (absolute priority) | ||
| if let Some(files) = &self.files { | ||
| for file in files { | ||
| // Check both exact match and ends_with | ||
| if normalized == *file || normalized.ends_with(file) { | ||
| return true; // Files overrides exclude | ||
| } | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The files array matching logic has a critical bug. The ends_with check on line 204 will cause false positives. For example, if files contains "test.ts", it will incorrectly match "./another_test.ts", "./src/my_test.ts", etc.
Additionally, the files are not normalized in the constructor (line 100), but the matched path is normalized (prefixed with ./). If files contains "test.ts" and the path is "./test.ts", the exact match on line 204 will fail.
Fix:
// In constructor, normalize files:
files: files.map(|f| Self::normalize_patterns(f, &tsconfig_dir)),
// In matches method, use exact match only:
if let Some(files) = &self.files {
    for file in files {
        if normalized == *file {
            return true;
        }
    }
    if self.include_patterns.is_empty() {
        return false;
    }
}Spotted by Graphite Agent
Is this helpful? React 👍 or 👎 to let us know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds valid.
| // Check if the importer file matches the tsconfig's include/exclude patterns | ||
| // Only check for actual files, not directories (directories are used when there's no specific importer) | ||
| // If the importer doesn't match, don't use this tsconfig's path mappings | ||
| let is_file = cached_path.meta(&self.cache.fs).is_some_and(|m| m.is_file); | ||
| if is_file && !tsconfig.matches_file(cached_path.path()) { | ||
| return Ok(None); | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this does not work for "Solution Style" tsconfigs, as we need to pick the referenced tsconfig that matches the include & exclude (can be done in a separate PR).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this file is not used and can be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this file is also not used and can be removed.
| // Default include (no include specified, defaults to **/*) - Exclude: [dist] | ||
| // All files except dist/ can use path mappings | ||
| ("with_baseurl", "index.ts", "~/log", true, "file in root can use path mapping"), | ||
| ("with_baseurl", "index.ts", "log", true, "file in root can use baseUrl"), | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need a test case that ensures dist cannot use path mappings. (That would cover the case where the value is a directory)
| let fixture_dir = f.join(fixture); | ||
| let resolver = Resolver::new(ResolveOptions { | ||
| tsconfig: Some(TsconfigDiscovery::Manual(TsconfigOptions { | ||
| config_file: fixture_dir.join("tsconfig.json"), | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should include / exclude be respected when the file path is specified? (I guess no?)
| // 1. Check files array first (absolute priority) | ||
| if let Some(files) = &self.files { | ||
| for file in files { | ||
| // Check both exact match and ends_with | ||
| if normalized == *file || normalized.ends_with(file) { | ||
| return true; // Files overrides exclude | ||
| } | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds valid.
| // If files specified but no match, continue to include/exclude | ||
| // (unless include is empty) | ||
| if self.include_patterns.is_empty() { | ||
| return false; | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // If files specified but no match, continue to include/exclude | |
| // (unless include is empty) | |
| if self.include_patterns.is_empty() { | |
| return false; | |
| } | 
This looks duplicated.
        
          
                src/tsconfig/mod.rs
              
                Outdated
          
        
      | 
               | 
          ||
| // SPECIAL CASE: empty files + no include = skip file matching | ||
| let is_empty_case = self.files.as_ref().is_some_and(std::vec::Vec::is_empty) | ||
| && self.include.as_ref().is_none_or(std::vec::Vec::is_empty); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| && self.include.as_ref().is_none_or(std::vec::Vec::is_empty); | |
| && self.include.as_ref().is_some_and(std::vec::Vec::is_empty); | 
include defaults to **/* so None should not be treated as the same with vec![].
| use std::path::PathBuf; | ||
| 
               | 
          ||
| /// Helper to create a TsconfigFileMatcher from a fixture directory | ||
| fn create_matcher_from_fixture(fixture_name: &str) -> (TsconfigFileMatcher, PathBuf) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this function should exist. If this is a unit test, the TsconfigFileMatcher should be constructed directly. If this is an integration test, the function in the src should be used to construct TsconfigFileMatcher.
| #[test] | ||
| fn test_character_set_patterns() { | ||
| let (matcher, fixture_dir) = create_matcher_from_fixture("character_set_patterns"); | ||
| 
               | 
          ||
| let test_cases = [ | ||
| ("App.ts", true, "character set matches uppercase start"), | ||
| ("Button.ts", true, "character set matches uppercase start"), | ||
| ]; | ||
| 
               | 
          ||
| for (file_path, should_match, comment) in test_cases { | ||
| let full_path = fixture_dir.join(file_path); | ||
| let result = matcher.matches(&full_path); | ||
| assert_eq!(result, should_match, "{comment}: path={file_path}"); | ||
| } | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TypeScript does not support this pattern ([A-Z]*.ts) and will treat this as a literal.
- Fix critical bug in files array matching: normalize files in constructor and use exact match only (prevents false positives) - Fix include empty check: None should default to **/* not be treated as empty - Remove duplicate empty pattern check - Add tests for fixture files in dist/ and node_modules/ - Fix test assertions for extends inheritance and paths outside root - Update character set pattern test (TypeScript doesn't support [A-Z] syntax) - Fix outdir_exclude fixture to include .js and .d.ts files - Add test case verifying excluded dist directory cannot use path mappings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Implements support for TypeScript's
include,exclude, andfilesfields in tsconfig.json, enabling proper file filtering for tools like Vite and Rolldown.Key Features
**,*,?, character sets)include:["**/*"]when not specifiedexclude:["node_modules", "bower_components", "jspm_packages"]+ outDirfilesarray override exclude patterns${configDir}support in patternsfilesarray + noincludematches nothingImplementation Details
fast-globcrate (v1.0.0) for high-performance pattern matchingAPI
Added public method
TsConfig::matches_file():Tests if a file path matches the tsconfig's include/exclude/files patterns.
Test Coverage
Tests ported and augmented from vite-tsconfig-paths:
**)*,?)[A-Z])Closes #764
🤖 Generated with Claude Code