@diia-inhouse/oxc-config 4.0.3 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../oxlint/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAsC,MAAM,QAAQ,CAAA;AAE9E,KAAK,MAAM,GAAG,YAAY,GAAG;IAAE,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAojBzD,eAAO,MAAM,IAAI,EAAE,MAAuC,CAAA;AAE1D,eAAO,MAAM,UAAU,EAAE,MAKvB,CAAA;AAIF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,GAAI,QAAQ,YAAY,KAAG,YAOnD,CAAA"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../oxlint/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAsC,MAAM,QAAQ,CAAA;AAE9E,KAAK,MAAM,GAAG,YAAY,GAAG;IAAE,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAsjBzD,eAAO,MAAM,IAAI,EAAE,MAAuC,CAAA;AAE1D,eAAO,MAAM,UAAU,EAAE,MAKvB,CAAA;AAIF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,GAAI,QAAQ,YAAY,KAAG,YAOnD,CAAA"}
@@ -206,6 +206,8 @@ const baseConfig = {
206
206
  '@diia-inhouse/package/stale-overrides': 'warn',
207
207
  '@diia-inhouse/package/peers-installed-locally': 'error',
208
208
  '@diia-inhouse/package/exports-package-json': 'error',
209
+ '@diia-inhouse/package/no-dev-dependency-import': 'error',
210
+ '@diia-inhouse/package/no-undeclared-import': 'error',
209
211
  '@diia-inhouse/locale/no-hardcoded-cyrillic': 'error',
210
212
  '@diia-inhouse/code/no-promise-settimeout': 'error',
211
213
  '@diia-inhouse/code/no-inline-object-return-type': 'error',
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from 'node:fs'
2
- import { dirname, join, resolve } from 'node:path'
2
+ import { isBuiltin } from 'node:module'
3
+ import { dirname, join, relative, resolve } from 'node:path'
3
4
 
4
5
  const FIX_FLAGS = new Set(['--fix', '--fix-suggestions', '--fix-dangerously'])
5
6
  const DEPENDENCY_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']
@@ -56,6 +57,7 @@ const DEV_TOOL_PATTERNS = [
56
57
  /^rimraf$/,
57
58
  /^tsc-alias$/,
58
59
  /^tsc-watch$/,
60
+ /^@bufbuild\/buf$/,
59
61
  /^@diia-inhouse\/(test|oxc-config|configs|scaffold|eslint-config|eslint-plugin)$/,
60
62
  ]
61
63
  const DEFAULT_NODE_MAJOR = 24
@@ -63,6 +65,9 @@ const DEFAULT_AUTHOR = 'diia-team'
63
65
  const DEV_SCRIPT = 'diia-dev'
64
66
  const COMMITLINT_CONFIG = '@diia-inhouse/configs/dist/commitlint'
65
67
  const MADGE_TS_CONFIG = './tsconfig.json'
68
+ const RUNTIME_SOURCE_PATTERN = /^src\//
69
+ const TEST_SOURCE_PATTERN = /(^|\/)(tests?|__tests__|__mocks__)\/|\.(spec|test)\.[cm]?[jt]sx?$/
70
+ const RUNTIME_DEPENDENCY_FIELDS = ['dependencies', 'peerDependencies', 'optionalDependencies']
66
71
 
67
72
  const isFixRun = () => process.argv.some((arg) => FIX_FLAGS.has(arg))
68
73
 
@@ -614,7 +619,151 @@ function defineRule(name, { messages, schema = [], check, fix }) {
614
619
  }
615
620
  }
616
621
 
622
+ function importedPackage(specifier) {
623
+ if (typeof specifier !== 'string' || /^[./#]/.test(specifier) || isBuiltin(specifier)) {
624
+ return null
625
+ }
626
+
627
+ const [scope, name] = specifier.split('/')
628
+
629
+ return scope.startsWith('@') ? `${scope}/${name}` : scope
630
+ }
631
+
632
+ const isTypeOnly = (node, kindField) =>
633
+ node[kindField] === 'type' || (node.specifiers?.length > 0 && node.specifiers.every((specifier) => specifier[kindField] === 'type'))
634
+
635
+ function runtimeManifest(context) {
636
+ const path = findManifest(context.filename)
637
+ const entry = path && loadManifest(path)
638
+ const source = path && relative(dirname(path), resolve(context.filename))
639
+
640
+ return entry && RUNTIME_SOURCE_PATTERN.test(source) && !TEST_SOURCE_PATTERN.test(source) ? entry : null
641
+ }
642
+
643
+ const runtimeImports = (check) => ({
644
+ ImportDeclaration(node) {
645
+ if (!isTypeOnly(node, 'importKind')) {
646
+ check(node, node.source.value)
647
+ }
648
+ },
649
+ ExportNamedDeclaration(node) {
650
+ if (node.source && !isTypeOnly(node, 'exportKind')) {
651
+ check(node, node.source.value)
652
+ }
653
+ },
654
+ ExportAllDeclaration(node) {
655
+ if (node.exportKind !== 'type') {
656
+ check(node, node.source.value)
657
+ }
658
+ },
659
+ TSImportEqualsDeclaration(node) {
660
+ if (node.importKind !== 'type' && node.moduleReference.type === 'TSExternalModuleReference') {
661
+ check(node, node.moduleReference.expression.value)
662
+ }
663
+ },
664
+ CallExpression(node) {
665
+ const [argument] = node.arguments
666
+
667
+ if (node.callee.type === 'Identifier' && node.callee.name === 'require' && argument?.type === 'Literal') {
668
+ check(node, argument.value)
669
+ }
670
+ },
671
+ })
672
+
673
+ function isInstalled(entry, name) {
674
+ entry.installed ??= new Map()
675
+
676
+ if (!entry.installed.has(name)) {
677
+ const dirs = []
678
+
679
+ for (let dir = dirname(entry.path); !dirs.includes(dir); dir = dirname(dir)) {
680
+ dirs.push(dir)
681
+ }
682
+
683
+ entry.installed.set(
684
+ name,
685
+ dirs.some((dir) => existsSync(join(dir, 'node_modules', name))),
686
+ )
687
+ }
688
+
689
+ return entry.installed.get(name)
690
+ }
691
+
692
+ const noDevDependencyImport = {
693
+ meta: {
694
+ type: 'problem',
695
+ messages: {
696
+ devDependency:
697
+ '"{{ name }}" is only a devDependency, and the release image does not install devDependencies. If only types are imported, use `import type`; if the code needs it at runtime, declare it in dependencies, or in peerDependencies in a library.',
698
+ },
699
+ schema: [],
700
+ },
701
+ create(context) {
702
+ const entry = runtimeManifest(context)
703
+
704
+ if (!entry) {
705
+ return {}
706
+ }
707
+
708
+ const { data } = entry
709
+
710
+ return runtimeImports((node, specifier) => {
711
+ const name = importedPackage(specifier)
712
+ const isDevOnly =
713
+ name !== null &&
714
+ name !== data.name &&
715
+ Object.hasOwn(data.devDependencies ?? {}, name) &&
716
+ !RUNTIME_DEPENDENCY_FIELDS.some((field) => Object.hasOwn(data[field] ?? {}, name))
717
+
718
+ if (isDevOnly) {
719
+ context.report({ node, messageId: 'devDependency', data: { name } })
720
+ }
721
+ })
722
+ },
723
+ }
724
+
725
+ const noUndeclaredImport = {
726
+ meta: {
727
+ type: 'problem',
728
+ messages: {
729
+ undeclared:
730
+ '"{{ name }}" is not declared in package.json and resolves only because another package installs it. If only types are imported, use `import type`. Not auto-fixed, because package-lock.json must change with it: run `npm i {{ name }}`, or declare it in peerDependencies in a library.',
731
+ },
732
+ schema: [],
733
+ },
734
+ create(context) {
735
+ const entry = runtimeManifest(context)
736
+
737
+ if (!entry) {
738
+ return {}
739
+ }
740
+
741
+ const { data } = entry
742
+ const declared = allDependencies(data)
743
+ const check = (node, specifier) => {
744
+ const name = importedPackage(specifier)
745
+
746
+ if (name !== null && name !== data.name && !Object.hasOwn(declared, name) && isInstalled(entry, name)) {
747
+ context.report({ node, messageId: 'undeclared', data: { name } })
748
+ }
749
+ }
750
+
751
+ return {
752
+ ...runtimeImports(check),
753
+ ImportExpression(node) {
754
+ if (node.source.type === 'Literal') {
755
+ check(node, node.source.value)
756
+ }
757
+ },
758
+ }
759
+ },
760
+ }
761
+
617
762
  export default {
618
763
  meta: { name: '@diia-inhouse/oxlint-plugin-package' },
619
- rules: Object.fromEntries(Object.entries(manifestRules).map(([name, rule]) => [name, defineRule(name, rule)])),
764
+ rules: {
765
+ ...Object.fromEntries(Object.entries(manifestRules).map(([name, rule]) => [name, defineRule(name, rule)])),
766
+ 'no-dev-dependency-import': noDevDependencyImport,
767
+ 'no-undeclared-import': noUndeclaredImport,
768
+ },
620
769
  }
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from 'node:fs'
2
- import { dirname, join, resolve } from 'node:path'
2
+ import { isBuiltin } from 'node:module'
3
+ import { dirname, join, relative, resolve } from 'node:path'
3
4
 
4
5
  const FIX_FLAGS = new Set(['--fix', '--fix-suggestions', '--fix-dangerously'])
5
6
  const DEPENDENCY_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']
@@ -56,6 +57,7 @@ const DEV_TOOL_PATTERNS = [
56
57
  /^rimraf$/,
57
58
  /^tsc-alias$/,
58
59
  /^tsc-watch$/,
60
+ /^@bufbuild\/buf$/,
59
61
  /^@diia-inhouse\/(test|oxc-config|configs|scaffold|eslint-config|eslint-plugin)$/,
60
62
  ]
61
63
  const DEFAULT_NODE_MAJOR = 24
@@ -63,6 +65,9 @@ const DEFAULT_AUTHOR = 'diia-team'
63
65
  const DEV_SCRIPT = 'diia-dev'
64
66
  const COMMITLINT_CONFIG = '@diia-inhouse/configs/dist/commitlint'
65
67
  const MADGE_TS_CONFIG = './tsconfig.json'
68
+ const RUNTIME_SOURCE_PATTERN = /^src\//
69
+ const TEST_SOURCE_PATTERN = /(^|\/)(tests?|__tests__|__mocks__)\/|\.(spec|test)\.[cm]?[jt]sx?$/
70
+ const RUNTIME_DEPENDENCY_FIELDS = ['dependencies', 'peerDependencies', 'optionalDependencies']
66
71
 
67
72
  const isFixRun = () => process.argv.some((arg) => FIX_FLAGS.has(arg))
68
73
 
@@ -614,7 +619,151 @@ function defineRule(name, { messages, schema = [], check, fix }) {
614
619
  }
615
620
  }
616
621
 
622
+ function importedPackage(specifier) {
623
+ if (typeof specifier !== 'string' || /^[./#]/.test(specifier) || isBuiltin(specifier)) {
624
+ return null
625
+ }
626
+
627
+ const [scope, name] = specifier.split('/')
628
+
629
+ return scope.startsWith('@') ? `${scope}/${name}` : scope
630
+ }
631
+
632
+ const isTypeOnly = (node, kindField) =>
633
+ node[kindField] === 'type' || (node.specifiers?.length > 0 && node.specifiers.every((specifier) => specifier[kindField] === 'type'))
634
+
635
+ function runtimeManifest(context) {
636
+ const path = findManifest(context.filename)
637
+ const entry = path && loadManifest(path)
638
+ const source = path && relative(dirname(path), resolve(context.filename))
639
+
640
+ return entry && RUNTIME_SOURCE_PATTERN.test(source) && !TEST_SOURCE_PATTERN.test(source) ? entry : null
641
+ }
642
+
643
+ const runtimeImports = (check) => ({
644
+ ImportDeclaration(node) {
645
+ if (!isTypeOnly(node, 'importKind')) {
646
+ check(node, node.source.value)
647
+ }
648
+ },
649
+ ExportNamedDeclaration(node) {
650
+ if (node.source && !isTypeOnly(node, 'exportKind')) {
651
+ check(node, node.source.value)
652
+ }
653
+ },
654
+ ExportAllDeclaration(node) {
655
+ if (node.exportKind !== 'type') {
656
+ check(node, node.source.value)
657
+ }
658
+ },
659
+ TSImportEqualsDeclaration(node) {
660
+ if (node.importKind !== 'type' && node.moduleReference.type === 'TSExternalModuleReference') {
661
+ check(node, node.moduleReference.expression.value)
662
+ }
663
+ },
664
+ CallExpression(node) {
665
+ const [argument] = node.arguments
666
+
667
+ if (node.callee.type === 'Identifier' && node.callee.name === 'require' && argument?.type === 'Literal') {
668
+ check(node, argument.value)
669
+ }
670
+ },
671
+ })
672
+
673
+ function isInstalled(entry, name) {
674
+ entry.installed ??= new Map()
675
+
676
+ if (!entry.installed.has(name)) {
677
+ const dirs = []
678
+
679
+ for (let dir = dirname(entry.path); !dirs.includes(dir); dir = dirname(dir)) {
680
+ dirs.push(dir)
681
+ }
682
+
683
+ entry.installed.set(
684
+ name,
685
+ dirs.some((dir) => existsSync(join(dir, 'node_modules', name))),
686
+ )
687
+ }
688
+
689
+ return entry.installed.get(name)
690
+ }
691
+
692
+ const noDevDependencyImport = {
693
+ meta: {
694
+ type: 'problem',
695
+ messages: {
696
+ devDependency:
697
+ '"{{ name }}" is only a devDependency, and the release image does not install devDependencies. If only types are imported, use `import type`; if the code needs it at runtime, declare it in dependencies, or in peerDependencies in a library.',
698
+ },
699
+ schema: [],
700
+ },
701
+ create(context) {
702
+ const entry = runtimeManifest(context)
703
+
704
+ if (!entry) {
705
+ return {}
706
+ }
707
+
708
+ const { data } = entry
709
+
710
+ return runtimeImports((node, specifier) => {
711
+ const name = importedPackage(specifier)
712
+ const isDevOnly =
713
+ name !== null &&
714
+ name !== data.name &&
715
+ Object.hasOwn(data.devDependencies ?? {}, name) &&
716
+ !RUNTIME_DEPENDENCY_FIELDS.some((field) => Object.hasOwn(data[field] ?? {}, name))
717
+
718
+ if (isDevOnly) {
719
+ context.report({ node, messageId: 'devDependency', data: { name } })
720
+ }
721
+ })
722
+ },
723
+ }
724
+
725
+ const noUndeclaredImport = {
726
+ meta: {
727
+ type: 'problem',
728
+ messages: {
729
+ undeclared:
730
+ '"{{ name }}" is not declared in package.json and resolves only because another package installs it. If only types are imported, use `import type`. Not auto-fixed, because package-lock.json must change with it: run `npm i {{ name }}`, or declare it in peerDependencies in a library.',
731
+ },
732
+ schema: [],
733
+ },
734
+ create(context) {
735
+ const entry = runtimeManifest(context)
736
+
737
+ if (!entry) {
738
+ return {}
739
+ }
740
+
741
+ const { data } = entry
742
+ const declared = allDependencies(data)
743
+ const check = (node, specifier) => {
744
+ const name = importedPackage(specifier)
745
+
746
+ if (name !== null && name !== data.name && !Object.hasOwn(declared, name) && isInstalled(entry, name)) {
747
+ context.report({ node, messageId: 'undeclared', data: { name } })
748
+ }
749
+ }
750
+
751
+ return {
752
+ ...runtimeImports(check),
753
+ ImportExpression(node) {
754
+ if (node.source.type === 'Literal') {
755
+ check(node, node.source.value)
756
+ }
757
+ },
758
+ }
759
+ },
760
+ }
761
+
617
762
  export default {
618
763
  meta: { name: '@diia-inhouse/oxlint-plugin-package' },
619
- rules: Object.fromEntries(Object.entries(manifestRules).map(([name, rule]) => [name, defineRule(name, rule)])),
764
+ rules: {
765
+ ...Object.fromEntries(Object.entries(manifestRules).map(([name, rule]) => [name, defineRule(name, rule)])),
766
+ 'no-dev-dependency-import': noDevDependencyImport,
767
+ 'no-undeclared-import': noUndeclaredImport,
768
+ },
620
769
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diia-inhouse/oxc-config",
3
- "version": "4.0.3",
3
+ "version": "5.0.1",
4
4
  "description": "OXC toolchain configs for Diia services: oxlint, oxfmt, oxc transformer",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "author": "diia-team",
@@ -30,7 +30,7 @@
30
30
  "semantic-release": "semantic-release"
31
31
  },
32
32
  "dependencies": {
33
- "@boundaries/eslint-plugin": "7.2.0",
33
+ "@boundaries/eslint-plugin": "7.2.1",
34
34
  "@diia-inhouse/eslint-plugin": "1.9.13",
35
35
  "@stylistic/eslint-plugin": "5.10.0",
36
36
  "eslint-import-resolver-oxc": "0.15.0",