node-opcua-convert-nodeset-to-javascript 2.180.0 → 2.181.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.
Files changed (49) hide show
  1. package/dist/_dataType.d.ts +2 -2
  2. package/dist/_dataType.js +27 -27
  3. package/dist/_dataType.js.map +1 -1
  4. package/dist/convert_namespace_to_typescript.d.ts +1 -1
  5. package/dist/convert_namespace_to_typescript.js +15 -14
  6. package/dist/convert_namespace_to_typescript.js.map +1 -1
  7. package/dist/convert_to_typescript.d.ts +4 -2
  8. package/dist/convert_to_typescript.js +79 -57
  9. package/dist/convert_to_typescript.js.map +1 -1
  10. package/dist/index.d.ts +3 -3
  11. package/dist/index.js +3 -3
  12. package/dist/index.js.map +1 -1
  13. package/dist/main.js +31 -7
  14. package/dist/main.js.map +1 -1
  15. package/dist/package_root.d.ts +4 -0
  16. package/dist/package_root.js +25 -0
  17. package/dist/package_root.js.map +1 -0
  18. package/dist/private/cache.d.ts +1 -1
  19. package/dist/private/cache.js +5 -5
  20. package/dist/private/cache.js.map +1 -1
  21. package/dist/private/get_corresponding_data_type.d.ts +2 -2
  22. package/dist/private/get_corresponding_data_type.js +6 -6
  23. package/dist/private/get_corresponding_data_type.js.map +1 -1
  24. package/dist/private/utils.js +2 -2
  25. package/dist/private/utils.js.map +1 -1
  26. package/dist/private-stuff.d.ts +4 -4
  27. package/dist/private-stuff.js +4 -4
  28. package/dist/private-stuff.js.map +1 -1
  29. package/dist/remove_unused.js +8 -3
  30. package/dist/remove_unused.js.map +1 -1
  31. package/dist/update_parent_tsconfig.js +3 -6
  32. package/dist/update_parent_tsconfig.js.map +1 -1
  33. package/dist/utils2.d.ts +15 -1
  34. package/dist/utils2.js +27 -22
  35. package/dist/utils2.js.map +1 -1
  36. package/package.json +17 -16
  37. package/source/_dataType.ts +5 -5
  38. package/source/convert_namespace_to_typescript.ts +10 -9
  39. package/source/convert_to_typescript.ts +35 -10
  40. package/source/index.ts +3 -3
  41. package/source/main.ts +30 -5
  42. package/source/package_root.ts +21 -0
  43. package/source/private/cache.ts +2 -2
  44. package/source/private/get_corresponding_data_type.ts +3 -3
  45. package/source/private/utils.ts +2 -2
  46. package/source/private-stuff.ts +4 -4
  47. package/source/remove_unused.ts +7 -2
  48. package/source/update_parent_tsconfig.ts +2 -4
  49. package/source/utils2.ts +40 -22
@@ -8,11 +8,11 @@ import type { IBasicSessionBrowseAsyncSimple, IBasicSessionReadAsyncSimple } fro
8
8
  import { type ReferenceDescription, StructureDefinition } from "node-opcua-types";
9
9
  import { LineFile } from "node-opcua-utils";
10
10
  import { DataType } from "node-opcua-variant";
11
- import { _exportDataTypeToTypescript } from "./_dataType";
12
- import type { Options } from "./options";
13
- import { type Cache, constructCache, type Import, makeTypeNameNew, type RequestedSubSymbol } from "./private/cache";
14
- import { getCorrespondingJavascriptType2 } from "./private/get_corresponding_data_type";
15
- import { toFilename } from "./private/to_filename";
11
+ import { _exportDataTypeToTypescript } from "./_dataType.js";
12
+ import type { Options } from "./options.js";
13
+ import { type Cache, constructCache, type Import, makeTypeNameNew, type RequestedSubSymbol } from "./private/cache.js";
14
+ import { getCorrespondingJavascriptType2 } from "./private/get_corresponding_data_type.js";
15
+ import { toFilename } from "./private/to_filename.js";
16
16
  import {
17
17
  extractBasicDataType,
18
18
  getBrowseName,
@@ -26,8 +26,8 @@ import {
26
26
  getSubtypeNodeIdIfAny,
27
27
  getTypeDefOrBaseType,
28
28
  getValueRank
29
- } from "./private/utils";
30
- import { f1, f2, quotifyIfNecessary, toComment, toJavascritPropertyName } from "./utils2";
29
+ } from "./private/utils.js";
30
+ import { f1, f2, quotifyIfNecessary, toComment, toJavascritPropertyName } from "./utils2.js";
31
31
 
32
32
  const warningLog = make_warningLog("typescript");
33
33
  const debugLog = make_debugLog("typescript");
@@ -399,7 +399,10 @@ async function _extractLocalMembers(
399
399
  if (!browseName.name) {
400
400
  throw new Error(`Expecting a browseName.name for node ${nodeId.toString()}`);
401
401
  }
402
- const name = toJavascritPropertyName(browseName.name, { ignoreConflictingName: true });
402
+ const name = toJavascritPropertyName(browseName.name, {
403
+ ignoreConflictingName: true,
404
+ parentNodeClass: classMember.nodeClass
405
+ });
403
406
 
404
407
  const description = await getDescription(session, nodeId);
405
408
  const modellingRule = await getModellingRule(session, nodeId);
@@ -533,12 +536,26 @@ async function extractVariableExtra(
533
536
  }
534
537
 
535
538
  interface ClassDefinitionB {
539
+ /** the class of the type; its instances are of the matching instance class */
540
+ nodeClass?: NodeClass.VariableType | NodeClass.ObjectType;
536
541
  superType?: {
537
542
  nodeId: NodeId;
538
543
  };
539
544
  interfaceName: Import;
540
545
  baseClassDef?: ClassDefinition | null;
541
546
  }
547
+
548
+ /** the class of the instances of a type: what a generated interface describes */
549
+ function instanceNodeClass(typeNodeClass: NodeClass | undefined): NodeClass | undefined {
550
+ switch (typeNodeClass) {
551
+ case NodeClass.ObjectType:
552
+ return NodeClass.Object;
553
+ case NodeClass.VariableType:
554
+ return NodeClass.Variable;
555
+ default:
556
+ return typeNodeClass;
557
+ }
558
+ }
542
559
  export async function extractClassMemberDef(
543
560
  session: IBasicSessionReadAsyncSimple & IBasicSessionBrowseAsyncSimple,
544
561
  nodeId: NodeId,
@@ -551,7 +568,10 @@ export async function extractClassMemberDef(
551
568
  if (!browseName.name) {
552
569
  throw new Error(`Expecting a browseName.name for node ${nodeId.toString()}`);
553
570
  }
554
- const name = toJavascritPropertyName(browseName.name, { ignoreConflictingName: true });
571
+ const name = toJavascritPropertyName(browseName.name, {
572
+ ignoreConflictingName: true,
573
+ parentNodeClass: instanceNodeClass(parentDef.nodeClass)
574
+ });
555
575
 
556
576
  if (nodeClass !== NodeClass.Method && nodeClass !== NodeClass.Object && nodeClass !== NodeClass.Variable) {
557
577
  throw new Error(`Invalid property ${NodeClass[nodeClass]} ${browseName?.toString()} ${nodeId.toString()}`);
@@ -740,6 +760,8 @@ function dumpChildren(padding: string, children: ClassMemberBasic[], f: LineFile
740
760
  continue;
741
761
  }
742
762
  cache.ensureImported(childType);
763
+ // name went through the rule already, with its parent class known; this is the same
764
+ // transformation applied to an already adjusted name, hence no parent class here
743
765
  const adjustedName = toJavascritPropertyName(name, { ignoreConflictingName: true });
744
766
  if (description.text) {
745
767
  f.write(`${padding}/**`);
@@ -807,7 +829,10 @@ function dumpUsedExport(currentType: string, namespaceIndex: number, cache: Cach
807
829
  )) {
808
830
  const filename = toFilename(symbol);
809
831
  const subSymbolList = getSubSymbolList(s);
810
- f.write(`import { ${subSymbolList.join(", ")} } from "./${filename}"`);
832
+ // ".js": ESM has no extension search, and the generated files are shipped
833
+ // source. Without it every regeneration would undo the extensions across
834
+ // ~1000 nodeset files and fail the drift check.
835
+ f.write(`import { ${subSymbolList.join(", ")} } from "./${filename}.js"`);
811
836
  }
812
837
  } else {
813
838
  if (cache.requestedSymbols.namespace[ns]) {
package/source/index.ts CHANGED
@@ -1,3 +1,3 @@
1
- export * from "./convert_namespace_to_typescript";
2
- export * from "./convert_to_typescript";
3
- export * from "./walk_through";
1
+ export * from "./convert_namespace_to_typescript.js";
2
+ export * from "./convert_to_typescript.js";
3
+ export * from "./walk_through.js";
package/source/main.ts CHANGED
@@ -2,13 +2,16 @@ import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
 
5
- import { AddressSpace, PseudoSession } from "node-opcua-address-space";
5
+ import { AddressSpace, type BaseNode, childAccessorNamesShadowedBy, PseudoSession, type UAObject } from "node-opcua-address-space";
6
6
  import { generateAddressSpace } from "node-opcua-address-space/nodeJS";
7
+ import { NodeClass } from "node-opcua-data-model";
7
8
  import { constructNodesetFilename, nodesetCatalog } from "node-opcua-nodesets";
8
9
 
9
- import { convertNamespaceTypeToTypescript } from "./convert_namespace_to_typescript";
10
- import { cleanUpTypescriptModule } from "./remove_unused";
11
- import { updateParentTSConfig } from "./update_parent_tsconfig";
10
+ import { convertNamespaceTypeToTypescript } from "./convert_namespace_to_typescript.js";
11
+ import { packagesFolder } from "./package_root.js";
12
+ import { cleanUpTypescriptModule } from "./remove_unused.js";
13
+ import { updateParentTSConfig } from "./update_parent_tsconfig.js";
14
+ import { setChildAccessorShadowing } from "./utils2.js";
12
15
 
13
16
  async function runWithConcurrency<T>(items: T[], limit: number, fn: (item: T) => Promise<void>): Promise<void> {
14
17
  let cursor = 0;
@@ -28,9 +31,31 @@ async function main() {
28
31
  const xmlFiles = nodesetCatalog.map((set) => constructNodesetFilename(set.xmlFile));
29
32
  await generateAddressSpace(addressSpace, xmlFiles);
30
33
 
34
+ // the names a generated interface must escape are the ones the runtime cannot expose on a
35
+ // node of that class, read off a node of each class rather than listed here
36
+ const server = addressSpace.rootFolder.objects.getFolderElementByName("Server") as UAObject | null;
37
+ const sampleNode: Partial<Record<NodeClass, BaseNode | null | undefined>> = {
38
+ [NodeClass.Object]: server,
39
+ [NodeClass.Variable]: server?.getPropertyByName("NamespaceArray"),
40
+ [NodeClass.Method]: server?.getMethodByName("GetMonitoredItems")
41
+ };
42
+ const shadowedByClass = new Map<NodeClass, ReadonlySet<string>>();
43
+ setChildAccessorShadowing((nodeClass) => {
44
+ let shadowed = shadowedByClass.get(nodeClass);
45
+ if (!shadowed) {
46
+ const node = sampleNode[nodeClass];
47
+ if (!node) {
48
+ return undefined;
49
+ }
50
+ shadowed = childAccessorNamesShadowedBy(node);
51
+ shadowedByClass.set(nodeClass, shadowed);
52
+ }
53
+ return shadowed;
54
+ });
55
+
31
56
  const session = new PseudoSession(addressSpace);
32
57
  const options = {
33
- baseFolder: path.join(__dirname, "../../"),
58
+ baseFolder: packagesFolder,
34
59
  prefix: "node-opcua-nodeset-"
35
60
  };
36
61
  // const nodesetCatalog2 = nodesetCatalog.filter((meta) => meta.name == "scales");
@@ -0,0 +1,21 @@
1
+ import path from "node:path";
2
+
3
+ /**
4
+ * The one place this package learns where it sits on disk.
5
+ *
6
+ * Everything else derives from `packagesFolder` rather than recomputing it, so the
7
+ * ESM migration has a single line to change here instead of five scattered across
8
+ * four files. `import.meta.dirname` cannot be used while this package emits CommonJS
9
+ * (TS1470), which is why the flip has to wait for the package to become "type": "module".
10
+ *
11
+ * Deliberately not `process.cwd()`: this package declares a `bin`, so it can be invoked
12
+ * as a CLI from any directory, and cwd would then resolve somewhere else entirely.
13
+ * A tool has to find its own files relative to itself.
14
+ */
15
+ const here = __dirname;
16
+
17
+ /** this package's own root, one level above the compiled output */
18
+ export const packageRoot = path.join(here, "..");
19
+
20
+ /** the monorepo's packages/ folder, which is where generated nodeset packages are written */
21
+ export const packagesFolder = path.join(here, "../../");
@@ -5,8 +5,8 @@ import { AttributeIds, NodeClass, QualifiedName } from "node-opcua-data-model";
5
5
  import { type NodeId, resolveNodeId } from "node-opcua-nodeid";
6
6
  import type { IBasicSessionReadAsyncSimple } from "node-opcua-pseudo-session";
7
7
  import { type DataTypeDefinition, EnumDefinition } from "node-opcua-types";
8
- import type { Options } from "../options";
9
- import { getBrowseName, getDefinition } from "./utils";
8
+ import type { Options } from "../options.js";
9
+ import { getBrowseName, getDefinition } from "./utils.js";
10
10
 
11
11
  export interface Import {
12
12
  name: string;
@@ -1,9 +1,9 @@
1
1
  import type { NodeId } from "node-opcua-nodeid";
2
2
  import type { IBasicSessionBrowseAsyncSimple, IBasicSessionReadAsyncSimple } from "node-opcua-pseudo-session";
3
3
  import { DataType } from "node-opcua-variant";
4
- import type { Cache } from "./cache";
5
- import { type Import, referenceEnumeration, referenceExtensionObject } from "./cache";
6
- import { _convertNodeIdToDataTypeAsync, getValueRank } from "./utils";
4
+ import type { Cache } from "./cache.js";
5
+ import { type Import, referenceEnumeration, referenceExtensionObject } from "./cache.js";
6
+ import { _convertNodeIdToDataTypeAsync, getValueRank } from "./utils.js";
7
7
 
8
8
  export async function getCorrespondingJavascriptType2(
9
9
  session: IBasicSessionReadAsyncSimple & IBasicSessionBrowseAsyncSimple,
@@ -7,8 +7,8 @@ import type { IBasicSessionBrowseAsyncSimple, IBasicSessionReadAsyncSimple } fro
7
7
  import { type BrowseResult, type DataTypeDefinition, ReferenceDescription } from "node-opcua-types";
8
8
  import { DataType } from "node-opcua-variant";
9
9
 
10
- const debugLog = make_debugLog(__filename);
11
- const doDebug = checkDebugFlag(__filename);
10
+ const debugLog = make_debugLog("utils");
11
+ const doDebug = checkDebugFlag("utils");
12
12
 
13
13
  export async function getDefinition(session: IBasicSessionReadAsyncSimple, nodeId: NodeId): Promise<DataTypeDefinition | null> {
14
14
  const dataValue = await session.read({ nodeId, attributeId: AttributeIds.DataTypeDefinition });
@@ -1,4 +1,4 @@
1
- export * from "./index";
2
- export * from "./options";
3
- export * from "./private/cache";
4
- export * from "./private/utils";
1
+ export * from "./index.js";
2
+ export * from "./options.js";
3
+ export * from "./private/cache.js";
4
+ export * from "./private/utils.js";
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  import type { ImportDeclarationStructure, OptionalKind, SourceFile } from "ts-morph";
5
5
  import { Node, Project, SyntaxKind } from "ts-morph";
6
6
 
7
- import { retryOnTransientWrite } from "./private/fs_retry";
7
+ import { retryOnTransientWrite } from "./private/fs_retry.js";
8
8
 
9
9
  const dryRun = false;
10
10
 
@@ -133,7 +133,12 @@ function regroupImports(file: SourceFile) {
133
133
  const ga = importGroup(a.moduleSpecifier);
134
134
  const gb = importGroup(b.moduleSpecifier);
135
135
  if (ga !== gb) return ga - gb;
136
- return a.moduleSpecifier.localeCompare(b.moduleSpecifier);
136
+ // Code point, not localeCompare. Two reasons. localeCompare is locale-dependent,
137
+ // so generated output committed from one machine could differ on another, and
138
+ // this output is committed and drift-checked. And its collation ranks "." after
139
+ // "_", which flips "./x.js" past "./x_y.js" the moment extensions are added,
140
+ // disagreeing with biome's ordering and taking the lint gate red.
141
+ return a.moduleSpecifier < b.moduleSpecifier ? -1 : a.moduleSpecifier > b.moduleSpecifier ? 1 : 0;
137
142
  });
138
143
 
139
144
  file.insertImportDeclarations(0, structures);
@@ -6,14 +6,12 @@ import { existsSync, promises as fsPromises } from "node:fs";
6
6
  const { readFile, writeFile } = fsPromises;
7
7
 
8
8
  import path from "node:path";
9
- // Uncomment the following to get __dirname equivalent when compiling to ESM
10
- //import { fileURLToPath } from 'url';
11
- //const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
9
 
13
10
  import { nodesetCatalog } from "node-opcua-nodesets";
14
11
 
12
+ import { packagesFolder } from "./package_root.js";
13
+
15
14
  export async function updateParentTSConfig() {
16
- const packagesFolder = path.join(__dirname, "..", "..");
17
15
  const parentTSConfigFile = path.join(packagesFolder, "tsconfig.json");
18
16
  console.log(`Updating parent tsconfig.json file: ${parentTSConfigFile}`);
19
17
  const content = await readFile(parentTSConfigFile, "utf8");
package/source/utils2.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { NodeClass } from "node-opcua-data-model";
1
2
  import { lowerFirstLetter } from "node-opcua-utils";
2
3
  import wrap from "wordwrap";
3
4
 
@@ -11,31 +12,48 @@ export function toComment(prefix: string, description: string) {
11
12
  .join("\n");
12
13
  }
13
14
 
14
- // to avoid clashes
15
- export function toJavascritPropertyName(childName: string, { ignoreConflictingName }: { ignoreConflictingName: boolean }): string {
15
+ /**
16
+ * the names under which the runtime never exposes a child of a node of the given class, by the
17
+ * rule node-opcua-address-space applies (`childAccessorNamesShadowedBy`); registered by main()
18
+ * once the address space is loaded, absent in a unit test
19
+ */
20
+ export type ChildAccessorShadowing = (parentNodeClass: NodeClass) => ReadonlySet<string> | undefined;
21
+
22
+ let childAccessorShadowing: ChildAccessorShadowing | undefined;
23
+
24
+ export function setChildAccessorShadowing(provider: ChildAccessorShadowing | undefined): void {
25
+ childAccessorShadowing = provider;
26
+ }
27
+
28
+ /**
29
+ * the names the generated interfaces have always escaped: kept whatever the runtime rule says, so
30
+ * that an interface published under one of them does not change under its users
31
+ */
32
+ const historicallyEscaped = new Set([
33
+ "namespaceUri",
34
+ "rolePermissions",
35
+ "displayName",
36
+ "eventNotifier",
37
+ "description",
38
+ "decode",
39
+ "encode"
40
+ ]);
41
+
42
+ /**
43
+ * the TypeScript property name of a child: its browse name lower-cased the way the runtime does,
44
+ * and `$`-prefixed when the runtime could not expose it under that name on a node of
45
+ * `parentNodeClass` (a child named NamespaceUri, DataType on a variable, Then on anything)
46
+ */
47
+ export function toJavascritPropertyName(
48
+ childName: string,
49
+ { ignoreConflictingName, parentNodeClass }: { ignoreConflictingName: boolean; parentNodeClass?: NodeClass }
50
+ ): string {
16
51
  childName = lowerFirstLetter(childName);
17
52
 
18
53
  if (ignoreConflictingName) {
19
- if (childName === "namespaceUri") {
20
- childName = "$namespaceUri";
21
- }
22
- if (childName === "rolePermissions") {
23
- childName = "$rolePermissions";
24
- }
25
- if (childName === "displayName") {
26
- childName = "$displayName";
27
- }
28
- if (childName === "eventNotifier") {
29
- childName = "$eventNotifier";
30
- }
31
- if (childName === "description") {
32
- childName = "$description";
33
- }
34
- if (childName === "decode") {
35
- childName = "$decode";
36
- }
37
- if (childName === "encode") {
38
- childName = "$encode";
54
+ const shadowed = parentNodeClass === undefined ? undefined : childAccessorShadowing?.(parentNodeClass);
55
+ if (historicallyEscaped.has(childName) || shadowed?.has(childName)) {
56
+ childName = `$${childName}`;
39
57
  }
40
58
  }
41
59
  return childName.replace(/</g, "$").replace(/>/g, "$").replace(/ |\./g, "_").replace(/#/g, "_");