mirror of
https://github.com/bitwarden/browser
synced 2026-02-06 11:43:51 +00:00
Task 1
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -54,3 +54,4 @@ apps/**/config/local.json
|
||||
|
||||
# Nx
|
||||
.nx
|
||||
strict-compliance-report.json
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Implementation Plan
|
||||
|
||||
- [-] 1. Set up infrastructure and tooling for strict mode migration
|
||||
- [x] 1. Set up infrastructure and tooling for strict mode migration
|
||||
|
||||
- Create utility scripts to identify files with @ts-strict-ignore comments
|
||||
- Implement automated testing for strict mode compliance
|
||||
|
||||
@@ -22,6 +22,12 @@
|
||||
"test:watch": "jest --clearCache && jest --watch",
|
||||
"test:watch:all": "jest --watchAll",
|
||||
"test:types": "node ./scripts/test-types.js",
|
||||
"test:strict": "node ./scripts/test-strict-compliance.js",
|
||||
"test:strict:all": "node ./scripts/test-strict-compliance.js all",
|
||||
"test:strict:plugin": "node ./scripts/test-strict-compliance.js plugin",
|
||||
"strict:find": "node ./scripts/strict-mode-utils.js find",
|
||||
"strict:progress": "node ./scripts/strict-mode-utils.js progress",
|
||||
"strict:test": "node ./scripts/strict-mode-utils.js test",
|
||||
"test:locales": "tsc --project ./scripts/tsconfig.json && node ./scripts/dist/test-locales.js",
|
||||
"lint:dep-ownership": "tsc --project ./scripts/tsconfig.json && node ./scripts/dist/dep-ownership.js",
|
||||
"docs:json": "compodoc -p ./tsconfig.json -e json -d . --disableRoutesGraph",
|
||||
|
||||
223
scripts/README-strict-mode.md
Normal file
223
scripts/README-strict-mode.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# TypeScript Strict Mode Migration Utilities
|
||||
|
||||
This directory contains utilities to help with the TypeScript strict mode migration process for the Bitwarden client codebase.
|
||||
|
||||
## Overview
|
||||
|
||||
The migration involves systematically enabling TypeScript strict mode across all applications and libraries. Currently, the codebase uses `typescript-strict-plugin` to allow gradual migration by excluding files with `@ts-strict-ignore` comments from strict checking.
|
||||
|
||||
## Utilities
|
||||
|
||||
### 1. Strict Mode Utils (`strict-mode-utils.js`)
|
||||
|
||||
A comprehensive utility for managing the strict mode migration process.
|
||||
|
||||
#### Commands
|
||||
|
||||
```bash
|
||||
# Find all files with @ts-strict-ignore comments
|
||||
node scripts/strict-mode-utils.js find
|
||||
|
||||
# Generate migration progress report
|
||||
node scripts/strict-mode-utils.js progress
|
||||
|
||||
# Test strict mode compliance for entire codebase
|
||||
node scripts/strict-mode-utils.js test
|
||||
|
||||
# Test strict mode compliance for specific project
|
||||
node scripts/strict-mode-utils.js test libs/platform
|
||||
|
||||
# Check typescript-strict-plugin status
|
||||
node scripts/strict-mode-utils.js plugin
|
||||
```
|
||||
|
||||
#### NPM Scripts
|
||||
|
||||
```bash
|
||||
# Find files with @ts-strict-ignore comments
|
||||
npm run strict:find
|
||||
|
||||
# Generate progress report
|
||||
npm run strict:progress
|
||||
|
||||
# Test strict mode compliance
|
||||
npm run strict:test
|
||||
```
|
||||
|
||||
### 2. Strict Compliance Tester (`test-strict-compliance.js`)
|
||||
|
||||
Automated testing for strict mode compliance across projects.
|
||||
|
||||
#### Commands
|
||||
|
||||
```bash
|
||||
# Test all projects for strict mode compliance
|
||||
node scripts/test-strict-compliance.js all
|
||||
|
||||
# Test projects matching a pattern
|
||||
node scripts/test-strict-compliance.js pattern libs/platform
|
||||
node scripts/test-strict-compliance.js pattern apps/
|
||||
|
||||
# Test current typescript-strict-plugin functionality
|
||||
node scripts/test-strict-compliance.js plugin
|
||||
```
|
||||
|
||||
#### NPM Scripts
|
||||
|
||||
```bash
|
||||
# Test all projects
|
||||
npm run test:strict:all
|
||||
|
||||
# Test typescript-strict-plugin
|
||||
npm run test:strict:plugin
|
||||
```
|
||||
|
||||
## Migration Process
|
||||
|
||||
### Current State
|
||||
|
||||
- **Total files with @ts-strict-ignore**: ~1,245 files
|
||||
- **TypeScript strict plugin**: Active (version 2.4.4)
|
||||
- **Base configuration**: `"strict": false` with plugin providing selective checking
|
||||
|
||||
### Migration Categories
|
||||
|
||||
The migration follows this order (as defined in the spec):
|
||||
|
||||
1. **Core Libraries** (`libs/platform`, `libs/common`)
|
||||
2. **Domain Libraries** (`libs/auth`, `libs/vault`, etc.)
|
||||
3. **UI Libraries** (`libs/components`, `libs/angular`)
|
||||
4. **Applications** (`apps/cli`, `apps/browser`, `apps/desktop`, `apps/web`)
|
||||
5. **Licensed Features** (`bitwarden_license/`)
|
||||
|
||||
### Per-Category Progress
|
||||
|
||||
| Category | Files with @ts-strict-ignore |
|
||||
| ------------------ | ---------------------------- |
|
||||
| libs/common | 381 files |
|
||||
| apps/web | 239 files |
|
||||
| apps/browser | 114 files |
|
||||
| bitwarden_license | 115 files |
|
||||
| other | 67 files |
|
||||
| libs/components | 62 files |
|
||||
| libs/tools | 54 files |
|
||||
| libs/angular | 51 files |
|
||||
| apps/cli | 45 files |
|
||||
| apps/desktop | 39 files |
|
||||
| libs/auth | 33 files |
|
||||
| libs/vault | 28 files |
|
||||
| libs/admin-console | 17 files |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Finding Files to Migrate
|
||||
|
||||
```bash
|
||||
# Get overview of migration status
|
||||
npm run strict:progress
|
||||
|
||||
# Find all files that need migration
|
||||
npm run strict:find
|
||||
```
|
||||
|
||||
### Testing Compliance
|
||||
|
||||
```bash
|
||||
# Test if a specific library is ready for strict mode
|
||||
node scripts/test-strict-compliance.js pattern libs/storage
|
||||
|
||||
# Test all projects (warning: this will take time and likely show many failures)
|
||||
npm run test:strict:all
|
||||
```
|
||||
|
||||
### During Migration
|
||||
|
||||
1. **Before starting a library migration**:
|
||||
|
||||
```bash
|
||||
# Check current status
|
||||
node scripts/test-strict-compliance.js pattern libs/platform
|
||||
```
|
||||
|
||||
2. **After fixing strict mode violations**:
|
||||
|
||||
```bash
|
||||
# Test compliance
|
||||
node scripts/test-strict-compliance.js pattern libs/platform
|
||||
|
||||
# Check progress
|
||||
npm run strict:progress
|
||||
```
|
||||
|
||||
3. **Final validation**:
|
||||
|
||||
```bash
|
||||
# Test that typescript-strict-plugin still works
|
||||
npm run test:strict:plugin
|
||||
|
||||
# Generate final report
|
||||
npm run strict:progress
|
||||
```
|
||||
|
||||
## Output Files
|
||||
|
||||
### `strict-compliance-report.json`
|
||||
|
||||
Generated by the compliance tester, contains:
|
||||
|
||||
- Timestamp of test run
|
||||
- Summary statistics (total, passed, failed, pass rate)
|
||||
- Detailed results for each project tested
|
||||
- Error details for failed projects
|
||||
|
||||
## Integration with Existing Tools
|
||||
|
||||
### Existing Type Checking
|
||||
|
||||
The existing `npm run test:types` command runs:
|
||||
|
||||
- `npx tsc-strict` (typescript-strict-plugin)
|
||||
- Type checking for all library tsconfig.json files
|
||||
|
||||
### New Strict Mode Testing
|
||||
|
||||
The new utilities complement existing tools by:
|
||||
|
||||
- Testing native TypeScript strict mode (without plugin)
|
||||
- Providing detailed progress tracking
|
||||
- Enabling targeted testing of specific projects
|
||||
- Generating comprehensive reports
|
||||
|
||||
## Migration Workflow
|
||||
|
||||
1. **Assessment**: Use `strict:progress` to see current state
|
||||
2. **Planning**: Use `strict:find` to identify files in target library
|
||||
3. **Testing**: Use `test-strict-compliance.js pattern <library>` to test current compliance
|
||||
4. **Implementation**: Fix strict mode violations in the library
|
||||
5. **Validation**: Re-test with compliance tester
|
||||
6. **Configuration**: Update library's tsconfig.json to enable strict mode
|
||||
7. **Final Check**: Ensure all tests pass and plugin still works
|
||||
|
||||
## Notes
|
||||
|
||||
- The utilities create temporary tsconfig files for testing but clean them up automatically
|
||||
- Failed tests include error output to help identify what needs to be fixed
|
||||
- The progress report shows the current state without making any changes
|
||||
- All utilities are safe to run and don't modify source code
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **"Cannot find module" errors**: Ensure you're running from the project root
|
||||
2. **Permission errors**: Make sure the scripts have execute permissions
|
||||
3. **TypeScript compilation errors**: These are expected during migration and indicate what needs to be fixed
|
||||
|
||||
### Getting Help
|
||||
|
||||
Run any utility without arguments to see usage information:
|
||||
|
||||
```bash
|
||||
node scripts/strict-mode-utils.js
|
||||
node scripts/test-strict-compliance.js
|
||||
```
|
||||
294
scripts/strict-mode-utils.js
Executable file
294
scripts/strict-mode-utils.js
Executable file
@@ -0,0 +1,294 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Utility scripts for TypeScript strict mode migration
|
||||
*
|
||||
* This script provides utilities to:
|
||||
* 1. Identify files with @ts-strict-ignore comments
|
||||
* 2. Test strict mode compliance
|
||||
* 3. Track migration progress
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { execSync } = require("child_process");
|
||||
|
||||
class StrictModeUtils {
|
||||
constructor() {
|
||||
this.rootDir = path.resolve(__dirname, "..");
|
||||
this.tsStrictIgnorePattern = /@ts-strict-ignore/;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively find all TypeScript files in a directory
|
||||
*/
|
||||
findTsFiles(dir, excludeDirs = ["node_modules", "dist", "coverage", ".git", ".angular"]) {
|
||||
const results = [];
|
||||
|
||||
try {
|
||||
const files = fs.readdirSync(dir);
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
if (!excludeDirs.includes(file)) {
|
||||
results.push(...this.findTsFiles(filePath, excludeDirs));
|
||||
}
|
||||
} else if (file.endsWith(".ts") && !file.endsWith(".d.ts")) {
|
||||
results.push(filePath);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Warning: Could not read directory ${dir}: ${error.message}`);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all files with @ts-strict-ignore comments
|
||||
*/
|
||||
findFilesWithStrictIgnore() {
|
||||
console.log("🔍 Scanning for files with @ts-strict-ignore comments...\n");
|
||||
|
||||
const tsFiles = this.findTsFiles(this.rootDir);
|
||||
const filesWithIgnore = [];
|
||||
|
||||
for (const filePath of tsFiles) {
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, "utf8");
|
||||
if (this.tsStrictIgnorePattern.test(content)) {
|
||||
const relativePath = path.relative(this.rootDir, filePath);
|
||||
filesWithIgnore.push({
|
||||
path: relativePath,
|
||||
fullPath: filePath,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Warning: Could not read file ${filePath}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return filesWithIgnore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a report of files with @ts-strict-ignore comments
|
||||
*/
|
||||
generateIgnoreReport() {
|
||||
const filesWithIgnore = this.findFilesWithStrictIgnore();
|
||||
|
||||
console.log(`📊 Found ${filesWithIgnore.length} files with @ts-strict-ignore comments:\n`);
|
||||
|
||||
// Group by directory for better organization
|
||||
const byDirectory = {};
|
||||
filesWithIgnore.forEach((file) => {
|
||||
const dir = path.dirname(file.path);
|
||||
if (!byDirectory[dir]) {
|
||||
byDirectory[dir] = [];
|
||||
}
|
||||
byDirectory[dir].push(file.path);
|
||||
});
|
||||
|
||||
// Sort directories and display
|
||||
const sortedDirs = Object.keys(byDirectory).sort();
|
||||
for (const dir of sortedDirs) {
|
||||
console.log(`📁 ${dir}/`);
|
||||
byDirectory[dir].forEach((file) => {
|
||||
const fileName = path.basename(file);
|
||||
console.log(` - ${fileName}`);
|
||||
});
|
||||
console.log();
|
||||
}
|
||||
|
||||
return filesWithIgnore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test strict mode compliance for a specific project
|
||||
*/
|
||||
testStrictCompliance(projectPath = null) {
|
||||
console.log("🧪 Testing TypeScript strict mode compliance...\n");
|
||||
|
||||
try {
|
||||
let command;
|
||||
if (projectPath) {
|
||||
const tsConfigPath = path.join(projectPath, "tsconfig.json");
|
||||
if (!fs.existsSync(tsConfigPath)) {
|
||||
throw new Error(`tsconfig.json not found at ${tsConfigPath}`);
|
||||
}
|
||||
command = `npx tsc --noEmit --strict --project ${tsConfigPath}`;
|
||||
console.log(`Testing project: ${projectPath}`);
|
||||
} else {
|
||||
command = "npx tsc --noEmit --strict";
|
||||
console.log("Testing entire codebase with strict mode...");
|
||||
}
|
||||
|
||||
console.log(`Running: ${command}\n`);
|
||||
|
||||
const output = execSync(command, {
|
||||
cwd: this.rootDir,
|
||||
encoding: "utf8",
|
||||
stdio: "pipe",
|
||||
});
|
||||
|
||||
console.log("✅ Strict mode compliance test passed!");
|
||||
if (output.trim()) {
|
||||
console.log("Output:", output);
|
||||
}
|
||||
|
||||
return { success: true, output };
|
||||
} catch (error) {
|
||||
console.log("❌ Strict mode compliance test failed!");
|
||||
console.log("Error output:");
|
||||
console.log(error.stdout || error.message);
|
||||
|
||||
return { success: false, error: error.stdout || error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if typescript-strict-plugin is being used
|
||||
*/
|
||||
checkStrictPlugin() {
|
||||
console.log("🔌 Checking typescript-strict-plugin usage...\n");
|
||||
|
||||
try {
|
||||
// Check if plugin is in package.json
|
||||
const packageJsonPath = path.join(this.rootDir, "package.json");
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
||||
const hasPlugin =
|
||||
packageJson.devDependencies && packageJson.devDependencies["typescript-strict-plugin"];
|
||||
|
||||
// Check if plugin is configured in tsconfig
|
||||
const tsConfigPath = path.join(this.rootDir, "tsconfig.base.json");
|
||||
const tsConfig = JSON.parse(fs.readFileSync(tsConfigPath, "utf8"));
|
||||
const hasPluginConfig =
|
||||
tsConfig.compilerOptions &&
|
||||
tsConfig.compilerOptions.plugins &&
|
||||
tsConfig.compilerOptions.plugins.some((p) => p.name === "typescript-strict-plugin");
|
||||
|
||||
console.log(`Plugin in package.json: ${hasPlugin ? "✅" : "❌"}`);
|
||||
console.log(`Plugin in tsconfig: ${hasPluginConfig ? "✅" : "❌"}`);
|
||||
|
||||
if (hasPlugin) {
|
||||
console.log(`Plugin version: ${packageJson.devDependencies["typescript-strict-plugin"]}`);
|
||||
}
|
||||
|
||||
return { hasPlugin, hasPluginConfig };
|
||||
} catch (error) {
|
||||
console.error("Error checking plugin status:", error.message);
|
||||
return { hasPlugin: false, hasPluginConfig: false };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate migration progress report
|
||||
*/
|
||||
generateProgressReport() {
|
||||
console.log("📈 Generating migration progress report...\n");
|
||||
|
||||
const filesWithIgnore = this.findFilesWithStrictIgnore();
|
||||
const pluginStatus = this.checkStrictPlugin();
|
||||
|
||||
// Count files by category
|
||||
const categories = {
|
||||
"libs/platform": 0,
|
||||
"libs/common": 0,
|
||||
"libs/auth": 0,
|
||||
"libs/vault": 0,
|
||||
"libs/key-management": 0,
|
||||
"libs/admin-console": 0,
|
||||
"libs/billing": 0,
|
||||
"libs/tools": 0,
|
||||
"libs/components": 0,
|
||||
"libs/angular": 0,
|
||||
"apps/cli": 0,
|
||||
"apps/browser": 0,
|
||||
"apps/desktop": 0,
|
||||
"apps/web": 0,
|
||||
bitwarden_license: 0,
|
||||
other: 0,
|
||||
};
|
||||
|
||||
filesWithIgnore.forEach((file) => {
|
||||
let categorized = false;
|
||||
for (const category of Object.keys(categories)) {
|
||||
if (file.path.startsWith(category)) {
|
||||
categories[category]++;
|
||||
categorized = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!categorized) {
|
||||
categories.other++;
|
||||
}
|
||||
});
|
||||
|
||||
console.log("Migration Progress by Category:");
|
||||
console.log("================================");
|
||||
|
||||
for (const [category, count] of Object.entries(categories)) {
|
||||
if (count > 0) {
|
||||
console.log(`${category.padEnd(25)} ${count.toString().padStart(3)} files`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nTotal files with @ts-strict-ignore: ${filesWithIgnore.length}`);
|
||||
console.log(`TypeScript strict plugin active: ${pluginStatus.hasPlugin ? "Yes" : "No"}`);
|
||||
|
||||
return {
|
||||
totalFiles: filesWithIgnore.length,
|
||||
categories,
|
||||
pluginActive: pluginStatus.hasPlugin,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// CLI interface
|
||||
if (require.main === module) {
|
||||
const utils = new StrictModeUtils();
|
||||
const command = process.argv[2];
|
||||
|
||||
switch (command) {
|
||||
case "find":
|
||||
utils.generateIgnoreReport();
|
||||
break;
|
||||
|
||||
case "test":
|
||||
const projectPath = process.argv[3];
|
||||
utils.testStrictCompliance(projectPath);
|
||||
break;
|
||||
|
||||
case "progress":
|
||||
utils.generateProgressReport();
|
||||
break;
|
||||
|
||||
case "plugin":
|
||||
utils.checkStrictPlugin();
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("TypeScript Strict Mode Migration Utilities");
|
||||
console.log("==========================================");
|
||||
console.log("");
|
||||
console.log("Usage: node scripts/strict-mode-utils.js <command>");
|
||||
console.log("");
|
||||
console.log("Commands:");
|
||||
console.log(" find - Find all files with @ts-strict-ignore comments");
|
||||
console.log(" test - Test strict mode compliance (optionally for specific project)");
|
||||
console.log(" progress - Generate migration progress report");
|
||||
console.log(" plugin - Check typescript-strict-plugin status");
|
||||
console.log("");
|
||||
console.log("Examples:");
|
||||
console.log(" node scripts/strict-mode-utils.js find");
|
||||
console.log(" node scripts/strict-mode-utils.js test");
|
||||
console.log(" node scripts/strict-mode-utils.js test libs/platform");
|
||||
console.log(" node scripts/strict-mode-utils.js progress");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StrictModeUtils;
|
||||
344
scripts/test-strict-compliance.js
Executable file
344
scripts/test-strict-compliance.js
Executable file
@@ -0,0 +1,344 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Automated testing script for TypeScript strict mode compliance
|
||||
*
|
||||
* This script tests strict mode compliance across different parts of the codebase
|
||||
* and provides detailed reporting for the migration process.
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { execSync, spawn } = require("child_process");
|
||||
const StrictModeUtils = require("./strict-mode-utils");
|
||||
|
||||
class StrictComplianceTester {
|
||||
constructor() {
|
||||
this.rootDir = path.resolve(__dirname, "..");
|
||||
this.utils = new StrictModeUtils();
|
||||
this.results = {
|
||||
passed: [],
|
||||
failed: [],
|
||||
skipped: [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all TypeScript configuration files in the project
|
||||
*/
|
||||
getTsConfigFiles() {
|
||||
const tsConfigs = [];
|
||||
|
||||
// Find all tsconfig.json files
|
||||
const findTsConfigs = (
|
||||
dir,
|
||||
excludeDirs = ["node_modules", "dist", "coverage", ".git", ".angular"],
|
||||
) => {
|
||||
try {
|
||||
const files = fs.readdirSync(dir);
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
if (!excludeDirs.includes(file)) {
|
||||
findTsConfigs(filePath, excludeDirs);
|
||||
}
|
||||
} else if (file === "tsconfig.json") {
|
||||
const relativePath = path.relative(this.rootDir, filePath);
|
||||
// Skip shared directory as mentioned in test-types.js
|
||||
if (!relativePath.includes("libs/shared/")) {
|
||||
tsConfigs.push({
|
||||
path: relativePath,
|
||||
fullPath: filePath,
|
||||
directory: path.dirname(relativePath),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Warning: Could not read directory ${dir}: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
findTsConfigs(this.rootDir);
|
||||
return tsConfigs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test strict mode compliance for a specific TypeScript configuration
|
||||
*/
|
||||
async testProjectStrictCompliance(tsConfigInfo) {
|
||||
const { path: configPath, directory } = tsConfigInfo;
|
||||
|
||||
console.log(`\n🧪 Testing ${directory}...`);
|
||||
|
||||
try {
|
||||
// Create a temporary strict tsconfig for testing
|
||||
const originalConfig = JSON.parse(fs.readFileSync(tsConfigInfo.fullPath, "utf8"));
|
||||
const testConfig = {
|
||||
...originalConfig,
|
||||
compilerOptions: {
|
||||
...originalConfig.compilerOptions,
|
||||
strict: true,
|
||||
// Enable all strict flags explicitly
|
||||
noImplicitAny: true,
|
||||
strictNullChecks: true,
|
||||
strictFunctionTypes: true,
|
||||
strictBindCallApply: true,
|
||||
strictPropertyInitialization: true,
|
||||
noImplicitReturns: true,
|
||||
noImplicitThis: true,
|
||||
},
|
||||
};
|
||||
|
||||
// Remove typescript-strict-plugin if present
|
||||
if (testConfig.compilerOptions.plugins) {
|
||||
testConfig.compilerOptions.plugins = testConfig.compilerOptions.plugins.filter(
|
||||
(plugin) => plugin.name !== "typescript-strict-plugin",
|
||||
);
|
||||
}
|
||||
|
||||
const tempConfigPath = path.join(
|
||||
path.dirname(tsConfigInfo.fullPath),
|
||||
"tsconfig.strict-test.json",
|
||||
);
|
||||
fs.writeFileSync(tempConfigPath, JSON.stringify(testConfig, null, 2));
|
||||
|
||||
try {
|
||||
const command = `npx tsc --noEmit --project ${tempConfigPath}`;
|
||||
const output = execSync(command, {
|
||||
cwd: this.rootDir,
|
||||
encoding: "utf8",
|
||||
stdio: "pipe",
|
||||
});
|
||||
|
||||
console.log(` ✅ ${directory} - PASSED`);
|
||||
this.results.passed.push({
|
||||
project: directory,
|
||||
configPath,
|
||||
output: output.trim(),
|
||||
});
|
||||
|
||||
return { success: true, project: directory, output };
|
||||
} finally {
|
||||
// Clean up temporary config file
|
||||
if (fs.existsSync(tempConfigPath)) {
|
||||
fs.unlinkSync(tempConfigPath);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(` ❌ ${directory} - FAILED`);
|
||||
|
||||
const errorOutput = error.stdout || error.stderr || error.message;
|
||||
this.results.failed.push({
|
||||
project: directory,
|
||||
configPath,
|
||||
error: errorOutput,
|
||||
});
|
||||
|
||||
return { success: false, project: directory, error: errorOutput };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test strict mode compliance for all projects
|
||||
*/
|
||||
async testAllProjects() {
|
||||
console.log("🚀 Starting comprehensive strict mode compliance testing...\n");
|
||||
|
||||
const tsConfigs = this.getTsConfigFiles();
|
||||
console.log(`Found ${tsConfigs.length} TypeScript configuration files to test.\n`);
|
||||
|
||||
// Test each project
|
||||
for (const tsConfig of tsConfigs) {
|
||||
await this.testProjectStrictCompliance(tsConfig);
|
||||
}
|
||||
|
||||
this.generateTestReport();
|
||||
return this.results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test specific projects by pattern
|
||||
*/
|
||||
async testProjectsByPattern(pattern) {
|
||||
console.log(`🎯 Testing projects matching pattern: ${pattern}\n`);
|
||||
|
||||
const tsConfigs = this.getTsConfigFiles();
|
||||
const matchingConfigs = tsConfigs.filter((config) => config.directory.includes(pattern));
|
||||
|
||||
if (matchingConfigs.length === 0) {
|
||||
console.log(`No projects found matching pattern: ${pattern}`);
|
||||
return this.results;
|
||||
}
|
||||
|
||||
console.log(`Found ${matchingConfigs.length} matching projects:\n`);
|
||||
matchingConfigs.forEach((config) => console.log(` - ${config.directory}`));
|
||||
console.log();
|
||||
|
||||
for (const tsConfig of matchingConfigs) {
|
||||
await this.testProjectStrictCompliance(tsConfig);
|
||||
}
|
||||
|
||||
this.generateTestReport();
|
||||
return this.results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a detailed test report
|
||||
*/
|
||||
generateTestReport() {
|
||||
console.log("\n📊 STRICT MODE COMPLIANCE TEST REPORT");
|
||||
console.log("=====================================\n");
|
||||
|
||||
const total =
|
||||
this.results.passed.length + this.results.failed.length + this.results.skipped.length;
|
||||
const passRate = total > 0 ? ((this.results.passed.length / total) * 100).toFixed(1) : 0;
|
||||
|
||||
console.log(`Total Projects Tested: ${total}`);
|
||||
console.log(`Passed: ${this.results.passed.length} (${passRate}%)`);
|
||||
console.log(`Failed: ${this.results.failed.length}`);
|
||||
console.log(`Skipped: ${this.results.skipped.length}\n`);
|
||||
|
||||
if (this.results.passed.length > 0) {
|
||||
console.log("✅ PASSED PROJECTS:");
|
||||
console.log("-------------------");
|
||||
this.results.passed.forEach((result) => {
|
||||
console.log(` ${result.project}`);
|
||||
});
|
||||
console.log();
|
||||
}
|
||||
|
||||
if (this.results.failed.length > 0) {
|
||||
console.log("❌ FAILED PROJECTS:");
|
||||
console.log("-------------------");
|
||||
this.results.failed.forEach((result) => {
|
||||
console.log(` ${result.project}`);
|
||||
// Show first few lines of error for context
|
||||
const errorLines = result.error.split("\n").slice(0, 3);
|
||||
errorLines.forEach((line) => {
|
||||
if (line.trim()) {
|
||||
console.log(` ${line.trim()}`);
|
||||
}
|
||||
});
|
||||
console.log();
|
||||
});
|
||||
}
|
||||
|
||||
// Save detailed report to file
|
||||
const reportPath = path.join(this.rootDir, "strict-compliance-report.json");
|
||||
fs.writeFileSync(
|
||||
reportPath,
|
||||
JSON.stringify(
|
||||
{
|
||||
timestamp: new Date().toISOString(),
|
||||
summary: {
|
||||
total,
|
||||
passed: this.results.passed.length,
|
||||
failed: this.results.failed.length,
|
||||
skipped: this.results.skipped.length,
|
||||
passRate: parseFloat(passRate),
|
||||
},
|
||||
results: this.results,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
console.log(`📄 Detailed report saved to: ${reportPath}\n`);
|
||||
|
||||
if (this.results.failed.length === 0) {
|
||||
console.log("🎉 All projects are strict mode compliant!");
|
||||
} else {
|
||||
console.log(
|
||||
`⚠️ ${this.results.failed.length} projects need attention before enabling strict mode.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test current typescript-strict-plugin functionality
|
||||
*/
|
||||
testStrictPlugin() {
|
||||
console.log("🔌 Testing typescript-strict-plugin functionality...\n");
|
||||
|
||||
try {
|
||||
const command = "npx tsc-strict";
|
||||
console.log(`Running: ${command}`);
|
||||
|
||||
const output = execSync(command, {
|
||||
cwd: this.rootDir,
|
||||
encoding: "utf8",
|
||||
stdio: "pipe",
|
||||
});
|
||||
|
||||
console.log("✅ typescript-strict-plugin test passed!");
|
||||
if (output.trim()) {
|
||||
console.log("Output:", output);
|
||||
}
|
||||
|
||||
return { success: true, output };
|
||||
} catch (error) {
|
||||
console.log("❌ typescript-strict-plugin test failed!");
|
||||
console.log("Error:", error.stdout || error.message);
|
||||
|
||||
return { success: false, error: error.stdout || error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CLI interface
|
||||
if (require.main === module) {
|
||||
const tester = new StrictComplianceTester();
|
||||
const command = process.argv[2];
|
||||
const pattern = process.argv[3];
|
||||
|
||||
async function main() {
|
||||
switch (command) {
|
||||
case "all":
|
||||
await tester.testAllProjects();
|
||||
break;
|
||||
|
||||
case "pattern":
|
||||
if (!pattern) {
|
||||
console.error("Error: Pattern required for pattern command");
|
||||
console.log("Usage: node scripts/test-strict-compliance.js pattern <pattern>");
|
||||
process.exit(1);
|
||||
}
|
||||
await tester.testProjectsByPattern(pattern);
|
||||
break;
|
||||
|
||||
case "plugin":
|
||||
tester.testStrictPlugin();
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("TypeScript Strict Mode Compliance Tester");
|
||||
console.log("========================================");
|
||||
console.log("");
|
||||
console.log("Usage: node scripts/test-strict-compliance.js <command> [options]");
|
||||
console.log("");
|
||||
console.log("Commands:");
|
||||
console.log(" all - Test all projects for strict mode compliance");
|
||||
console.log(" pattern - Test projects matching a specific pattern");
|
||||
console.log(" plugin - Test current typescript-strict-plugin functionality");
|
||||
console.log("");
|
||||
console.log("Examples:");
|
||||
console.log(" node scripts/test-strict-compliance.js all");
|
||||
console.log(" node scripts/test-strict-compliance.js pattern libs/platform");
|
||||
console.log(" node scripts/test-strict-compliance.js pattern apps/");
|
||||
console.log(" node scripts/test-strict-compliance.js plugin");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("Error:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = StrictComplianceTester;
|
||||
Reference in New Issue
Block a user