Skip to content

Commit fdddbc1

Browse files
committed
Merge remote-tracking branch 'origin/main' into chenjiahan/fix-type-aware-lint
2 parents 7f45762 + 8535fc2 commit fdddbc1

5 files changed

Lines changed: 82 additions & 11 deletions

File tree

.github/workflows/test.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ on:
1212

1313
permissions:
1414
contents: read
15+
pull-requests: read
1516

1617
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
1718
jobs:
@@ -26,28 +27,47 @@ jobs:
2627
- name: Checkout
2728
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
2829

30+
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
31+
id: changes
32+
with:
33+
predicate-quantifier: 'every'
34+
filters: |
35+
changed:
36+
- "!**/*.md"
37+
- "!**/*.mdx"
38+
- "!**/_meta.json"
39+
- "!**/_nav.json"
40+
- "!**/dictionary.txt"
41+
2942
- name: Setup Node.js
43+
if: steps.changes.outputs.changed == 'true'
3044
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
3145
with:
3246
node-version: 24.18.1
3347
package-manager-cache: false
3448

3549
- name: Install Pnpm
50+
if: steps.changes.outputs.changed == 'true'
3651
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
3752
with:
3853
run_install: true
3954

4055
- name: Build Packages
56+
if: steps.changes.outputs.changed == 'true'
4157
run: node --run build
4258

4359
- name: Run Rust Tests
60+
if: steps.changes.outputs.changed == 'true'
4461
run: cargo test --profile ci --workspace --locked
4562

4663
- name: Build Native Binding
64+
if: steps.changes.outputs.changed == 'true'
4765
run: pnpm --filter rstack build:native:ci
4866

4967
- name: Check Generated Native Files
68+
if: steps.changes.outputs.changed == 'true'
5069
run: git diff --exit-code -- packages/rstack/binding.cjs packages/rstack/binding.d.cts
5170

5271
- name: Run Test
72+
if: steps.changes.outputs.changed == 'true'
5373
run: node --run test

packages/rstack/src/fmt/config.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@ type ResolveFmtConfigOptions = {
1717
type PathMatcher = (filePath: string) => boolean;
1818
type FmtOptionsResolver = (filePath: string) => ResolvedFmtOptions;
1919

20+
/**
21+
* Each path from the root represents an ordered sequence of matching overrides.
22+
* A node stores the options merged along that path.
23+
*/
24+
type OptionsCacheNode = {
25+
children: WeakMap<ResolvedFmtOptions, OptionsCacheNode>;
26+
options: ResolvedFmtOptions;
27+
};
28+
29+
const createOptionsCacheNode = (options: ResolvedFmtOptions): OptionsCacheNode => ({
30+
children: new WeakMap(),
31+
options,
32+
});
33+
2034
const neverMatches: PathMatcher = () => false;
2135

2236
const compileMatchers = (
@@ -96,22 +110,27 @@ const createOptionsResolver = (config: ResolvedFmtConfig): FmtOptionsResolver =>
96110
}
97111

98112
const resolveRelativePath = createRelativePathResolver(config.rootPath);
113+
const rootCacheNode = createOptionsCacheNode(config.baseOptions);
99114

100115
return (filePath) => {
101-
let options = config.baseOptions;
116+
let cacheNode = rootCacheNode;
102117
const relativeFilePath = resolveRelativePath(filePath);
103118

104119
for (const override of config.overrides) {
105120
if (!override.options || !override.matches(relativeFilePath)) {
106121
continue;
107122
}
108-
if (options === config.baseOptions) {
109-
options = { ...options };
123+
124+
// Reuse the merged result for this override after the current matched sequence.
125+
let nextCacheNode = cacheNode.children.get(override.options);
126+
if (!nextCacheNode) {
127+
nextCacheNode = createOptionsCacheNode({ ...cacheNode.options, ...override.options });
128+
cacheNode.children.set(override.options, nextCacheNode);
110129
}
111-
Object.assign(options, override.options);
130+
cacheNode = nextCacheNode;
112131
}
113132

114-
return options;
133+
return cacheNode.options;
115134
};
116135
};
117136

packages/rstack/src/fmt/plugins.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,12 @@ const createFingerprintResolver = (): FingerprintResolver => {
9494
/** Creates a project-root resolver for plugins in final per-file options. */
9595
const createPluginResolver = (rootPath: string): FmtPluginResolver => {
9696
const parentUrl = pathToFileURL(join(rootPath, 'index.js'));
97-
const cache = new Map<string, string>();
97+
const pluginCache = new Map<string, string>();
98+
const optionsCache = new WeakMap<ResolvedFmtOptions, ResolvedFmtOptions>();
9899

99100
const resolvePlugin = (plugin: FmtPluginSpecifier): string => {
100101
const specifier = plugin instanceof URL ? plugin.href : plugin;
101-
const cached = cache.get(specifier);
102+
const cached = pluginCache.get(specifier);
102103
if (cached !== undefined) {
103104
return cached;
104105
}
@@ -119,11 +120,16 @@ const createPluginResolver = (rootPath: string): FmtPluginResolver => {
119120
}
120121
}
121122

122-
cache.set(specifier, resolved);
123+
pluginCache.set(specifier, resolved);
123124
return resolved;
124125
};
125126

126127
return (options) => {
128+
const cached = optionsCache.get(options);
129+
if (cached !== undefined) {
130+
return cached;
131+
}
132+
127133
const { plugins } = options;
128134
if (!plugins?.length) {
129135
return options;
@@ -136,10 +142,11 @@ const createPluginResolver = (rootPath: string): FmtPluginResolver => {
136142
}
137143

138144
const resolvedPlugins = plugins.map(resolvePlugin);
139-
140-
return resolvedPlugins.every((plugin, index) => plugin === plugins[index])
145+
const resolvedOptions = resolvedPlugins.every((plugin, index) => plugin === plugins[index])
141146
? options
142147
: { ...options, plugins: resolvedPlugins };
148+
optionsCache.set(options, resolvedOptions);
149+
return resolvedOptions;
143150
};
144151
};
145152

packages/rstack/tests/fmt/config.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,29 @@ test('applies basename and path overrides in declaration order', () => {
5050
expect(config.baseOptions).toEqual({ singleQuote: false });
5151
});
5252

53+
test('reuses options for the same override combination', () => {
54+
const config = normalizeFmtConfig(
55+
{
56+
singleQuote: false,
57+
overrides: [
58+
{ files: '*.ts', options: { semi: false } },
59+
{ files: 'src/**/*.ts', options: { singleQuote: true } },
60+
],
61+
},
62+
rootPath,
63+
);
64+
const resolveOptions = createOptionsResolver(config);
65+
66+
const first = resolveOptions(path.join(rootPath, 'src/first.ts'));
67+
const second = resolveOptions(path.join(rootPath, 'src/second.ts'));
68+
const outside = resolveOptions(path.join(rootPath, 'outside.ts'));
69+
70+
expect(first).toBe(second);
71+
expect(first).not.toBe(outside);
72+
expect(first).toEqual({ semi: false, singleQuote: true });
73+
expect(outside).toEqual({ semi: false, singleQuote: false });
74+
});
75+
5376
test('applies overrides outside the config root', () => {
5477
const config = normalizeFmtConfig(
5578
{

packages/rstack/tests/fmt/plugins.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ test('resolves plugin specifiers from the config root', async () => {
4040
],
4141
};
4242

43-
const resolved = createPluginResolver(rootPath)(options);
43+
const resolvePlugins = createPluginResolver(rootPath);
44+
const resolved = resolvePlugins(options);
4445

4546
expect(resolved.plugins).toEqual([
4647
pathToFileURL(packageEntry).href,
@@ -50,6 +51,7 @@ test('resolves plugin specifiers from the config root', async () => {
5051
'data:text/javascript,export default {}',
5152
]);
5253
expect(options.plugins[0]).toBe('prettier-plugin-packagejson');
54+
expect(resolvePlugins(options)).toBe(resolved);
5355
});
5456
});
5557

0 commit comments

Comments
 (0)