Skip to content

Commit

Permalink
Convert tsconfig to use project references, see #1356
Browse files Browse the repository at this point in the history
  • Loading branch information
samreid committed Sep 20, 2024
1 parent b88b250 commit c892eec
Show file tree
Hide file tree
Showing 12 changed files with 987 additions and 145 deletions.
3 changes: 3 additions & 0 deletions eslint/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ module.exports = {
'@typescript-eslint/naming-convention': 'off', // TODO: We should decide on the conventions and enable this rule.

// Require .toString() to only be called on objects which provide useful information when stringified 🔒 💭
// TODO: See https://github.com/phetsims/chipper/issues/1466, this takes a very long time at runtime
'@typescript-eslint/no-base-to-string': 'error',

// Disallow non-null assertion in locations that may be confusing 🔒 🔧 🛠
Expand Down Expand Up @@ -233,6 +234,7 @@ module.exports = {
'@typescript-eslint/no-extraneous-class': 'off', // It is sometimes useful to have a class with static methods that can call each other

// Require Promise-like statements to be handled appropriately ✅ 🛠 💭
// TODO: See https://github.com/phetsims/chipper/issues/1466, this takes a very long time at runtime
'@typescript-eslint/no-floating-promises': 'error',

// Disallow iterating over an array with a for-in loop ✅ 💭
Expand Down Expand Up @@ -587,6 +589,7 @@ module.exports = {
'explicit-method-return-type': 'error',

// Variables that are Properties should end in "Property", like const myProperty = new Property();
// TODO: See https://github.com/phetsims/chipper/issues/1466, this takes a very long time at runtime
'require-property-suffix': 'error',

// Static fields should have the 'readonly' modifier
Expand Down
7 changes: 4 additions & 3 deletions js/grunt/lint.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,6 @@ function runEslint( repos, options ) {
showProgressBar: true
}, options );

const showProgressBar = options.showProgressBar && repos.length > 1;
showProgressBar && showCommandLineProgress( 0, false );

const patterns = repos.map( repo => `../${repo}/` );

const args = [ 'eslint' ];
Expand All @@ -83,7 +80,11 @@ function runEslint( repos, options ) {
}
}

const showProgressBar = options.showProgressBar && repos.length > 1;
showProgressBar && showCommandLineProgress( 0, false );

// Always write to the cache, even if it was cleared above.
// TODO: https://github.com/phetsims/chipper/issues/1356 do we need a different cache for different repos or repo combos? We get a speed boost when using repos.join as a filename key
args.push( '--cache', '--cache-location', '../chipper/eslint/cache/.eslintcache' );

// Add the '--fix' option if fix is true
Expand Down
3 changes: 1 addition & 2 deletions js/grunt/tasks/lint-all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,13 @@ const brands = getBrands( grunt, repo, buildLocal );
*/
export const lintAll = ( async () => {

console.log( ' task beginning' );
const lintReturnValue = await lint( getPhetLibs( repo, brands ), {
cache: cache,
fix: fix,
chipAway: chipAway
} );

// Output results on errors.
// Output results on errors.
if ( !lintReturnValue.ok ) {
grunt.fail.fatal( 'Lint failed' );
}
Expand Down
2 changes: 1 addition & 1 deletion js/grunt/tsc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const execute = require( '../../../perennial-alias/js/common/execute' );
*/
const tsc = async function( path: string, commandLineArgs: string[] = [] ): Promise<{ execResult: { stdout: string; stderr: string; code: number }; time: number }> {

const args = [ '../chipper/node_modules/typescript/bin/tsc', ...commandLineArgs ];
const args = [ '../chipper/node_modules/typescript/bin/tsc', '-b', ...commandLineArgs ];
return execute( 'node', args, path, {
errors: 'resolve'
} );
Expand Down
2 changes: 1 addition & 1 deletion js/scripts/absolute-tsc.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ if ( !args || args.length === 0 ) {

// console.log( 'changes detected...' );

const results = await execute( 'node', [ `${__dirname}/../../../chipper/node_modules/typescript/bin/tsc` ], args[ 0 ], {
const results = await execute( 'node', [ `${__dirname}/../../../chipper/node_modules/typescript/bin/tsc`, '-b' ], args[ 0 ], {
errors: 'resolve',

// TODO: it would be nice not to need this, https://github.com/phetsims/chipper/issues/1415
Expand Down
199 changes: 199 additions & 0 deletions js/scripts/addPackageJSONEslintConfigProject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// Copyright 2024, University of Colorado Boulder

/**
* Script: updateEslintConfig.js
* Description: Iterates through a list of repositories, checks for package.json,
* and updates or creates eslintConfig to include parserOptions.project.
*
* TODO: https://github.com/phetsims/chipper/issues/1356 delete script
* @author Sam Reid (PhET Interactive Simulations)
*/

// eslint-disable-next-line no-property-in-require-statement
const fs = require( 'fs' ).promises;
const path = require( 'path' );
const os = require( 'os' );

/**
* Utility function to check if a file exists.
* @param {string} filepath - Path to the file.
* @returns {Promise<boolean>} - True if exists, else false.
*/
async function fileExists( filepath ) {
try {
await fs.access( filepath );
return true;
}
catch( err ) {
return false;
}
}

/**
* Utility function to safely parse JSON.
* @param {string} data - JSON string.
* @param {string} repoName - Name of the repository (for logging).
* @returns {Object|null} - Parsed JSON object or null if error.
*/
function safeJsonParse( data, repoName ) {
try {
return JSON.parse( data );
}
catch( err ) {
console.error( `Error parsing JSON in "${repoName}": ${err.message}` );
return null;
}
}

/**
* Main function to execute the script logic.
*/
async function main() {
try {
const homeDir = os.homedir();
const rootDir = path.join( homeDir, 'phet', 'root' );
const activeReposPath = path.join( rootDir, 'perennial', 'data', 'active-repos' );

// Read the list of active repositories
const reposData = await fs.readFile( activeReposPath, 'utf-8' );
const repoNames = reposData.split( /\r?\n/ ).map( line => line.trim() ).filter( line => line.length > 0 );

console.log( `Found ${repoNames.length} active repositories.` );

for ( const repoName of repoNames ) {
const repoPath = path.join( rootDir, repoName );
const packageJsonPath = path.join( repoPath, 'package.json' );

const hasPackageJson = await fileExists( packageJsonPath );

if ( !hasPackageJson ) {
console.warn( `Skipping repository "${repoName}" as it lacks package.json.` );
continue;
}

console.log( `Processing repository: ${repoName}` );

// Read and parse package.json
let packageJson;
try {
const packageData = await fs.readFile( packageJsonPath, 'utf-8' );
packageJson = safeJsonParse( packageData, repoName );
if ( !packageJson ) {
console.error( `Skipping repository "${repoName}" due to JSON parse error.` );
continue;
}
}
catch( err ) {
console.error( `Error reading package.json in "${repoName}": ${err.message}` );
continue;
}

// Initialize eslintConfig if it doesn't exist
if ( !packageJson.eslintConfig ) {
packageJson.eslintConfig = {
extends: '../chipper/eslint/sim_eslintrc.js'
};
console.log( `Created eslintConfig for "${repoName}".` );
}
else {
// Ensure "extends" includes the required path
if ( !packageJson.eslintConfig.extends ) {
packageJson.eslintConfig.extends = '../chipper/eslint/sim_eslintrc.js';
console.log( `Added extends to eslintConfig for "${repoName}".` );
}
else if ( typeof packageJson.eslintConfig.extends === 'string' ) {
if ( !packageJson.eslintConfig.extends.includes( '../chipper/eslint/sim_eslintrc.js' ) ) {
// Convert to array if it's a string and doesn't include the required extend
packageJson.eslintConfig.extends = [
packageJson.eslintConfig.extends,
'../chipper/eslint/sim_eslintrc.js'
];
console.log( `Extended eslintConfig for "${repoName}".` );
}
}
else if ( Array.isArray( packageJson.eslintConfig.extends ) ) {
if ( !packageJson.eslintConfig.extends.includes( '../chipper/eslint/sim_eslintrc.js' ) ) {
packageJson.eslintConfig.extends.push( '../chipper/eslint/sim_eslintrc.js' );
console.log( `Appended extends to eslintConfig for "${repoName}".` );
}
}
// If eslintConfig.extends is neither string nor array, log a warning
else {
console.warn( `Unexpected format for eslintConfig.extends in "${repoName}". Skipping extends modification.` );
}
}

// Initialize overrides if it doesn't exist
if ( !packageJson.eslintConfig.overrides ) {
packageJson.eslintConfig.overrides = [];
console.log( `Created overrides array in eslintConfig for "${repoName}".` );
}

// Find an existing override for "**/*.ts"
let tsOverride = packageJson.eslintConfig.overrides.find( override => {
return Array.isArray( override.files ) && override.files.includes( '**/*.ts' );
} );

if ( !tsOverride ) {
// Create a new override for "**/*.ts"
tsOverride = {
files: [ '**/*.ts' ],
parserOptions: {
project: [ `../${repoName}/tsconfig.json` ]
}
};
packageJson.eslintConfig.overrides.push( tsOverride );
console.log( `Added new override for "**/*.ts" in "${repoName}".` );
}
else {
// Ensure parserOptions.project exists and includes the required path
if ( !tsOverride.parserOptions ) {
tsOverride.parserOptions = {
project: [ `../${repoName}/tsconfig.json` ]
};
console.log( `Added parserOptions to existing override in "${repoName}".` );
}
else {
if ( !tsOverride.parserOptions.project ) {
tsOverride.parserOptions.project = [ `../${repoName}/tsconfig.json` ];
console.log( `Added project to parserOptions in "${repoName}".` );
}
else {
// Ensure the project path is included
const projectPath = `../${repoName}/tsconfig.json`;
if ( !Array.isArray( tsOverride.parserOptions.project ) ) {
tsOverride.parserOptions.project = [ tsOverride.parserOptions.project ];
}
if ( !tsOverride.parserOptions.project.includes( projectPath ) ) {
tsOverride.parserOptions.project.push( projectPath );
console.log( `Appended project path to parserOptions in "${repoName}".` );
}
else {
console.log( `parserOptions.project already includes the path in "${repoName}".` );
}
}
}
}

// Write the updated package.json back to the file
try {
const formattedPackageJson = JSON.stringify( packageJson, null, 2 ) + '\n';
await fs.writeFile( packageJsonPath, formattedPackageJson, 'utf-8' );
console.log( `Updated package.json in "${repoName}".\n` );
}
catch( err ) {
console.error( `Error writing package.json in "${repoName}": ${err.message}` );
continue;
}
}

console.log( 'All applicable repositories have been processed.' );
}
catch( err ) {
console.error( `An unexpected error occurred: ${err.message}` );
process.exit( 1 );
}
}

// Execute the main function
main();
Loading

0 comments on commit c892eec

Please sign in to comment.