Fixing common TS typings

In the scenegraph I’m getting a few warnings in the typings. I’d like to fix them but it looks like there’s properties that I need to add to the definitions and others don’t make sense. Why can’t a scene node be used in a dictionary?

Matrix
[ts] Property ‘e’ does not exist on type ‘Matrix’. [2339]

var transform = item.transform;
transform.e = 0;

Module

[ts] Cannot find module ‘os’. [2307]

const platform = require("os").platform();

File

[ts] Type ‘File | File[ ]’ is not assignable to type ‘File’.
Type ‘File’ is missing the following properties from type ‘File’: lastModified, size, type, slice [2322]

 /**@type {File} */
 let selectedFile = await fileSystem.getFileForOpening();

[ts] Property ‘nativePath’ does not exist on type ‘File’. [2339]

var value = selectedFile.nativePath;

[ts] Property ‘read’ does not exist on type ‘Entry’. [2339]

const entries = await pluginDataFolder.getEntries();
const files = await entries.filter(entry => entry.name.indexOf(filename) >= 0);
var file = files[0];
let data = await file.read();

[ts] Property ‘write’ does not exist on type ‘Folder | File’.
Property ‘write’ does not exist on type ‘Folder’. [2339]

newFile.write(data);

[ts] Cannot find name ‘Folder’. [2304]

 /** @type { Folder } */
 const folder = await pluginFolder.getEntry(filePath);

Color

[ts] Property ‘serialize’ does not exist on type ‘Color’. [2339]

console.log("Color:" + new Color("red").serialize()); // returns "rgb(255, 0, 0)" 

SceneNode

[ts] Type ‘SceneNode’ cannot be used as an index type. [2538]

itemsDictionary[sceneNode] = nestLevel;
1 Like

As per the documentation, Matrix has no attribute e: Matrix · Adobe XD Plugin Reference

OS – according to the docs – is a global class and therefore not a module that can get imported by using require() – cf. OS · Adobe XD Plugin Reference

This could be an interference with the File type from the “standard” DOM typings (see File - Web APIs | MDN). I – unfortunately – haven’t found a way to get around that (except for disabling the DOM typings which would mean not having capabilities around the UI aspects).

I would guess that this is the same problem as above. Again, that’s due to an incompatibility of some aspects of the XD APIs with the way typescript handles things (more on that later :wink:)

That’s to be expected since at that point, file is only known to be an Entry, and only a File has the property read(). Unfortunately, that’s a point where there’s a big incompatibility between the way the APIs work and the way typescript works. With the APIs, we would simply check for the Entry being a File by using entry.isFile(), but TypeScript – unfortunately – doesn’t support something like this, which is why there is no good solution for this (at least none that I’m aware of and I’ve searched for a long time when developing the definitions).

Same as above.

Since Folder isn’t an exported member of the module, it seems to have difficulties to resolve that name. Unfortunately, I don’t know of any way around that (sometimes it works, sometimes it doesn’t).

While it’s interesting to hear about that serialize() method, it isn’t stated anywhere in the docs and therefore not inlcuded in the typings (since it is my goal to keep those up to date with the docs where I have docs to copy etc. and not with some “mysterious, hidden APIs” :wink:) – cf. Color · Adobe XD Plugin Reference

That’s due to the way TypeScript handles index types. Please refer to https://basarat.gitbooks.io/typescript/docs/types/index-signatures.html for further information about index signatures in anything related to TypeScript. The short version is, that you could use sceneNode.toString() to hack your way around that and otherwise can’t use complex data structures as index types (out of curiosity: why would you want to do that anyway? It seems a bit strange to me since once you loose the reference of an object, you’ll never be able to access the elements of the dictionary anymore?)

1 Like

Is DocumentRoot one of the types that is complicated to work around?

1 Like

@Velara

I’m not sure I can follow you here – if you’re referring to the RootNode class: That’s implemented in scenegraph.d.ts.

If you use it as a parameter (as second parameter of a command called by XD), you’ll need to specify the parameters type in the JSDoc (see the sample.js file for an example of that), since this is “your” function and the typings can’t specify argument types of functions declared elsewhere.

I hope this helps,
Best,
Pablo

1 Like

I was looking up type DocumentRoot. :stuck_out_tongue:

1 Like

While it’s interesting to hear about that serialize() method, it isn’t stated anywhere in the docs and therefore not inlcuded in the typings

Thank you for that by the way! :slight_smile:  If an API is undocumented, it’s not supported and it may change or be deleted at any time. It’s also always possible, in the future, that we may reject plugin submissions if they are using undocumented APIs that we consider unstable. So we strongly encourage everyone to only use APIs that are seen in our official public docs.

1 Like

Issue 1

This is due to the fact that the typings do not have the module defined. @pklaschka mentioned in a response that the docs define this as a global class. That appears to not be the case: according to Adobe devs on these forums, OS is indeed a module. Both documentation and typings updates will be required.

Issue 2

There are two errors here. The first error says that the response you get from the getFileForOpening() API call will return either a File or an Array of Files. Those objects will have different APIs and it is unsafe to “assume” one or the other. Your assumption is made explicity by your JSDoc-style type annotation. The second problem is that you are, as suggested by @pklaschka, referring to the Web DOM File definition. There are two ways around both of these issues.

Option 1

Import the UXP File type under a different name and use it. See:

const UXPFile = require('uxp').storage.File;

// ...

/**
 * @type {UXPFile|UXPFile[]}
 */
let selectedFile = await fileSystem.getFileForOpening();

Option 2

Make use of TypeScript’s “import types”. See:

/**
 * @type {import('uxp').storage.File|import('uxp').storage.File[]}
 */
let selectedFile = await fileSystem.getFileForOpening();

Both options will clear your errors and allow you to make use of the functions. Option 2 may be more verbose but it has the benefit of not actually importing the UXP File type into your module’s scope.

Issue 3

Assuming that you corrected the issues above, your selectedFile should at this point be of type UXPFile (considering Option 1 above). If these are in the same script, then you would want to take Option 1 because it allows you to narrow the type of your selectedFile to what you want. Specifically:

const UXPFile = require('uxp').storage.File;

// ...

if (selectedFile instanceof UXPFile)
{
    selectedFile.nativePath; // No more problems!
}

Issue 4

This is due to the fact that your file instance is typed as Entry, the superclass of both the UXP File and UXP Folder classes. You don’t actually know what type you have! You could have a Folder that has the same name as your filename parameter!

To both clear this error and program more safely, do the following:

const UXPFile = require('uxp').storage.File;
const UXPFolder = require('uxp').storage.Folder;

// ...

const entries = await pluginDataFolder.getEntries();
const files = await entries.filter(entry => entry.name.indexOf(filename) >= 0);
var file = files[0];
if (file instanceof UXPFile)
{
    let data = await file.read();
}
else if (file instanceof UXPFolder)
{
    // something else?
}

You may also wish to change the name of some of your variables to better reflect what you’re actually getting (e.g. filesfilesOrFolders).

Issue 5

You are being told that your newFile type is either a UXP Folder or a UXP File. If both of the types had a write method with the same signature (parameter number and type and return type) defined then there wouldn’t be a problem. However, the UXP Folder class doesn’t have such a method. As such, you need to be clear of your intentions:

const UXPFile = require('uxp').storage.File;
const UXPFolder = require('uxp').storage.Folder;

// ...

if (newFile instanceof UXPFile)
{
    newFile.write(data);
}

Issue 6

You haven’t imported the UXP Folder type into your scope. You can do this with the same two options suggested in the File version of this above.

However, when you do so, you will get a different error. The getEntry method does not return a Folder instance! It returns an Entry instance. That can be either a Folder or a File. You will therefore need to do something like this:

const UXPFile = require('uxp').storage.File;
const UXPFolder = require('uxp').storage.Folder;

// ...

// [NOTE] The JSDoc type was removed. The language service
//  should be able to figure out the type automatically. If you want
//  to add the type, it should be something equal to:
//    @type {import('uxp').storage.Entry};
const entry = await pluginFolder.getEntry(filePath);

if (entry instanceof UXPFile)
{
    // Do file stuff.
}
else if (entry instanceof UXPFolder)
{
    // Do folder stuff.
}

I hope that someone finds this information helpful!

2 Likes

@pklaschka This has been confirmed. OS is a module, not a global class. The typings will need to be updated.

@Velara Until the TypeScript typings are fixed you can get rid of the error with the following:

//@ts-ignore
const platform = require("os").platform();

Hope this helps!

3 Likes