Fix extensions enablement bugs (#8797)

This commit is contained in:
christine betts
2025-09-19 11:43:39 -04:00
committed by GitHub
parent 6e4236bf76
commit fa8cea170a
5 changed files with 353 additions and 83 deletions
@@ -15,6 +15,80 @@ export interface AllExtensionsEnablementConfig {
[extensionName: string]: ExtensionEnablementConfig;
}
export class Override {
constructor(
public baseRule: string,
public isDisable: boolean,
public includeSubdirs: boolean,
) {}
static fromInput(inputRule: string, includeSubdirs: boolean): Override {
const isDisable = inputRule.startsWith('!');
let baseRule = isDisable ? inputRule.substring(1) : inputRule;
baseRule = ensureLeadingAndTrailingSlash(baseRule);
return new Override(baseRule, isDisable, includeSubdirs);
}
static fromFileRule(fileRule: string): Override {
const isDisable = fileRule.startsWith('!');
let baseRule = isDisable ? fileRule.substring(1) : fileRule;
const includeSubdirs = baseRule.endsWith('*');
baseRule = includeSubdirs
? baseRule.substring(0, baseRule.length - 1)
: baseRule;
return new Override(baseRule, isDisable, includeSubdirs);
}
conflictsWith(other: Override): boolean {
if (this.baseRule === other.baseRule) {
return (
this.includeSubdirs !== other.includeSubdirs ||
this.isDisable !== other.isDisable
);
}
return false;
}
isEqualTo(other: Override): boolean {
return (
this.baseRule === other.baseRule &&
this.includeSubdirs === other.includeSubdirs &&
this.isDisable === other.isDisable
);
}
asRegex(): RegExp {
return globToRegex(`${this.baseRule}${this.includeSubdirs ? '*' : ''}`);
}
isChildOf(parent: Override) {
if (!parent.includeSubdirs) {
return false;
}
return parent.asRegex().test(this.baseRule);
}
output(): string {
return `${this.isDisable ? '!' : ''}${this.baseRule}${this.includeSubdirs ? '*' : ''}`;
}
matchesPath(path: string) {
return this.asRegex().test(path);
}
}
const ensureLeadingAndTrailingSlash = function (dirPath: string): string {
// Normalize separators to forward slashes for consistent matching across platforms.
let result = dirPath.replace(/\\/g, '/');
if (result.charAt(0) !== '/') {
result = '/' + result;
}
if (result.charAt(result.length - 1) !== '/') {
result = result + '/';
}
return result;
};
/**
* Converts a glob pattern to a RegExp object.
* This is a simplified implementation that supports `*`.
@@ -25,7 +99,7 @@ export interface AllExtensionsEnablementConfig {
function globToRegex(glob: string): RegExp {
const regexString = glob
.replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escape special regex characters
.replace(/\*/g, '.*'); // Convert * to .*
.replace(/(\/?)\*/g, '($1.*)?'); // Convert * to optional group
return new RegExp(`^${regexString}$`);
}
@@ -52,16 +126,13 @@ export class ExtensionEnablementManager {
const extensionConfig = config[extensionName];
// Extensions are enabled by default.
let enabled = true;
for (const rule of extensionConfig?.overrides ?? []) {
const isDisableRule = rule.startsWith('!');
const globPattern = isDisableRule ? rule.substring(1) : rule;
const regex = globToRegex(globPattern);
if (regex.test(currentPath)) {
enabled = !isDisableRule;
const allOverrides = extensionConfig?.overrides ?? [];
for (const rule of allOverrides) {
const override = Override.fromFileRule(rule);
if (override.matchesPath(ensureLeadingAndTrailingSlash(currentPath))) {
enabled = !override.isDisable;
}
}
return enabled;
}
@@ -96,24 +167,19 @@ export class ExtensionEnablementManager {
if (!config[extensionName]) {
config[extensionName] = { overrides: [] };
}
const pathWithGlob = `${scopePath}*`;
const pathWithoutGlob = scopePath;
const newPath = includeSubdirs ? pathWithGlob : pathWithoutGlob;
const conflictingPath = includeSubdirs ? pathWithoutGlob : pathWithGlob;
config[extensionName].overrides = config[extensionName].overrides.filter(
(rule) =>
rule !== conflictingPath &&
rule !== `!${conflictingPath}` &&
rule !== `!${newPath}`,
);
if (!config[extensionName].overrides.includes(newPath)) {
config[extensionName].overrides.push(newPath);
}
const override = Override.fromInput(scopePath, includeSubdirs);
const overrides = config[extensionName].overrides.filter((rule) => {
const fileOverride = Override.fromFileRule(rule);
if (
fileOverride.conflictsWith(override) ||
fileOverride.isEqualTo(override)
) {
return false; // Remove conflicts and equivalent values.
}
return !fileOverride.isChildOf(override);
});
overrides.push(override.output());
config[extensionName].overrides = overrides;
this.writeConfig(config);
}
@@ -122,30 +188,7 @@ export class ExtensionEnablementManager {
includeSubdirs: boolean,
scopePath: string,
): void {
const config = this.readConfig();
if (!config[extensionName]) {
config[extensionName] = { overrides: [] };
}
const pathWithGlob = `${scopePath}*`;
const pathWithoutGlob = scopePath;
const targetPath = includeSubdirs ? pathWithGlob : pathWithoutGlob;
const newRule = `!${targetPath}`;
const conflictingPath = includeSubdirs ? pathWithoutGlob : pathWithGlob;
config[extensionName].overrides = config[extensionName].overrides.filter(
(rule) =>
rule !== conflictingPath &&
rule !== `!${conflictingPath}` &&
rule !== targetPath,
);
if (!config[extensionName].overrides.includes(newRule)) {
config[extensionName].overrides.push(newRule);
}
this.writeConfig(config);
this.enable(extensionName, includeSubdirs, `!${scopePath}`);
}
remove(extensionName: string): void {