diff --git a/.eslintignore b/.eslintignore index bd75a25d1..feca7cba5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2014,6 +2014,12 @@ packages/tools/buildServerDocker.js.map packages/tools/buildServerDocker.test.d.ts packages/tools/buildServerDocker.test.js packages/tools/buildServerDocker.test.js.map +packages/tools/checkLibPaths.d.ts +packages/tools/checkLibPaths.js +packages/tools/checkLibPaths.js.map +packages/tools/checkLibPaths.test.d.ts +packages/tools/checkLibPaths.test.js +packages/tools/checkLibPaths.test.js.map packages/tools/convertThemesToCss.d.ts packages/tools/convertThemesToCss.js packages/tools/convertThemesToCss.js.map diff --git a/.gitignore b/.gitignore index 50dde0ceb..0c01599e3 100644 --- a/.gitignore +++ b/.gitignore @@ -2004,6 +2004,12 @@ packages/tools/buildServerDocker.js.map packages/tools/buildServerDocker.test.d.ts packages/tools/buildServerDocker.test.js packages/tools/buildServerDocker.test.js.map +packages/tools/checkLibPaths.d.ts +packages/tools/checkLibPaths.js +packages/tools/checkLibPaths.js.map +packages/tools/checkLibPaths.test.d.ts +packages/tools/checkLibPaths.test.js +packages/tools/checkLibPaths.test.js.map packages/tools/convertThemesToCss.d.ts packages/tools/convertThemesToCss.js packages/tools/convertThemesToCss.js.map diff --git a/lint-staged.config.js b/lint-staged.config.js index b11e6fc3c..89aedc688 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -12,6 +12,7 @@ module.exports = { // '**/*.ts?(x)': () => 'npm run tsc', '*.{js,jsx,ts,tsx}': [ 'yarn run linter-precommit', + 'yarn run checkLibPaths', 'git add', ], }; diff --git a/package.json b/package.json index a1f4a16a9..b5931d7d8 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "buildSettingJsonSchema": "yarn workspace joplin start settingschema ../../../joplin-website/docs/schema/settings.json", "buildTranslations": "node packages/tools/build-translation.js", "buildWebsite": "node ./packages/tools/website/build.js && yarn run buildPluginDoc && yarn run buildSettingJsonSchema", + "checkLibPaths": "node ./packages/tools/checkLibPaths.js", "circularDependencyCheck": "madge --warning --circular --extensions js ./", "clean": "npm run clean --workspaces --if-present && node packages/tools/clean && yarn cache clean", "dependencyTree": "madge", diff --git a/packages/tools/checkLibPaths.test.ts b/packages/tools/checkLibPaths.test.ts new file mode 100644 index 000000000..d409565df --- /dev/null +++ b/packages/tools/checkLibPaths.test.ts @@ -0,0 +1,36 @@ +import { findInvalidImportPaths } from './checkLibPaths'; + +describe('checkLibPaths', function() { + + test('should detect invalid lib paths', async () => { + const testCases: [number, string][] = [ + [1, ` + import time from '../lib/time'; + `], + [2, ` + import time from '../lib/time'; + import shim from '../lib/shim'; + `], + [1, ` + import time from '../lib/time'; + import shim from '@joplin/lib/shim'; + `], + [1, ` + import time from '@joplin/lib/time'; + import shim = require('../lib/shim'); + `], + [1, ` + import time from '@joplin/lib/time'; + import shim from '@joplin/lib/shim'; + const { isInsideContainer } = require('../lib/shim'); + `], + ]; + + for (const testCase of testCases) { + const [expected, input] = testCase; + const actual = findInvalidImportPaths(__dirname, input.split('\n').map(l => l.trim()).join('\n')); + expect(actual.length).toBe(expected); + } + }); + +}); diff --git a/packages/tools/checkLibPaths.ts b/packages/tools/checkLibPaths.ts new file mode 100644 index 000000000..6064e262e --- /dev/null +++ b/packages/tools/checkLibPaths.ts @@ -0,0 +1,55 @@ +/* eslint-disable import/prefer-default-export */ + +import { readFile } from 'fs-extra'; +import { normalize } from 'path'; +import yargs = require('yargs'); +import { dirname } from './tool-utils'; + +export const findInvalidImportPaths = (baseDir: string, fileContent: string): string[] => { + const output: string[] = []; + + // We expect the code to be in a specific format, as formatted by eslint. So + // checkLibPath must only be called after the linter hook. + + const regexes = [ + /^import .* from '(.*\.\.\/lib\/.*)'/gm, + /^import .* = require\('(.*\.\.\/lib\/.*)'\)/gm, + /^const .* = require\('(.*\.\.\/lib\/.*)'\)/gm, + + /^import .* from '(.*\.\.\/renderer\/.*)'/gm, + /^import .* = require\('(.*\.\.\/renderer\/.*)'\)/gm, + /^const .* = require\('(.*\.\.\/renderer\/.*)'\)/gm, + ]; + + for (const regex of regexes) { + while (true) { + const matches = regex.exec(fileContent); + if (!matches) break; + const [line, packagePath] = matches; + const fullPath = normalize(`${baseDir}/${packagePath}`); + if (fullPath.includes('packages/lib/') || fullPath.includes('packages/renderer/')) output.push(line); + } + + } + + return output; +}; + +const main = async () => { + const argv = await yargs.argv; + const filePaths = argv._ as string[]; + if (!filePaths || !filePaths.length) return; + + for (const filePath of filePaths) { + const content = await readFile(filePath, 'utf8'); + const invalidImportPaths = findInvalidImportPaths(dirname(filePath), content); + if (invalidImportPaths.length) throw new Error(`Invalid lib import paths in ${filePath}: ${invalidImportPaths.join(' / ')}`); + } +}; + +if (require.main === module) { + main().catch((error) => { + console.error(error); + process.exit(1); + }); +} diff --git a/packages/tools/package.json b/packages/tools/package.json index 5f6d989d4..40abbbc6c 100644 --- a/packages/tools/package.json +++ b/packages/tools/package.json @@ -23,6 +23,7 @@ "@joplin/lib": "~2.8", "@joplin/renderer": "~2.8", "@types/node-fetch": "1.6.9", + "@types/yargs": "16.0.3", "dayjs": "^1.10.7", "execa": "^4.1.0", "fs-extra": "^4.0.3", diff --git a/yarn.lock b/yarn.lock index 41e3e27d6..c41076f6e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3320,6 +3320,7 @@ __metadata: "@types/mustache": ^0.8.32 "@types/node": ^14.14.6 "@types/node-fetch": 1.6.9 + "@types/yargs": 16.0.3 dayjs: ^1.10.7 execa: ^4.1.0 fs-extra: ^4.0.3 @@ -5743,6 +5744,15 @@ __metadata: languageName: node linkType: hard +"@types/yargs@npm:16.0.3": + version: 16.0.3 + resolution: "@types/yargs@npm:16.0.3" + dependencies: + "@types/yargs-parser": "*" + checksum: 0968b06d2f6df764cb180a4089b293ae313a310b0c3524c296f93ac896ca1ed8d857b595db0388355f9f45772226d25d6a4f7df359302f245040a63ba057ae5a + languageName: node + linkType: hard + "@types/yargs@npm:^15.0.0": version: 15.0.14 resolution: "@types/yargs@npm:15.0.14"