Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@typescript-eslint/no-unsafe-assignment: Unsafe assignment of an any value

Consider the following code:

const defaultState = () => {
  return {
    profile: {
      id: '',
      displayName: '',
      givenName: '',
    },
    photo: '',
  }
}

const state = reactive(defaultState())

export const setGraphProfile = async () => {
  const response = await getGraphProfile()
  state.profile = { ...defaultState().profile, ...response.data }
}

Which generates the ESLint warning:

@typescript-eslint/no-unsafe-assignment: Unsafe assignment of an any value.

This means that the properties in response.data might not match the ones of the profile. The return of getGraphProfile is Promise<AxiosResponse<any>>. Of course it's easy to get rid of this ESLint warning by simply ignoring it:

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
state.profile = { ...defaultState().profile, ...response.data }

Questions:

  • How is it possible to shape the data in the Promise getGraphProfile so it does match? Because one can create a TS interface but that would simply create duplicate code with the object defaultState().profile
  • Why is TypeScript not having an issue with this code but the linter does? Do both not need to be alligned?

The implementations:

const callGraph = (
  url: string,
  token: string,
  axiosConfig?: AxiosRequestConfig
) => {
  const params: AxiosRequestConfig = {
    method: 'GET',
    url: url,
    headers: { Authorization: `Bearer ${token}` },
  }
  return axios({ ...params, ...axiosConfig })
}

const getGraphDetails = async (
  uri: string,
  scopes: string[],
  axiosConfig?: AxiosRequestConfig
) => {
  try {
    const response = await getToken(scopes)
    if (response && response.accessToken) {
      return callGraph(uri, response.accessToken, axiosConfig)
    } else {
      throw new Error('We could not get a token because of page redirect')
    }
  } catch (error) {
    throw new Error(`We could not get a token: ${error}`)
  }
}

export const getGraphProfile = async () => {
  try {
    return await getGraphDetails(
      config.resources.msGraphProfile.uri,
      config.resources.msGraphProfile.scopes
    )
  } catch (error) {
    throw new Error(`Failed retrieving the graph profile: ${error}`)
  }
}

export const getGraphPhoto = async () => {
  try {
    const response = await getGraphDetails(
      config.resources.msGraphPhoto.uri,
      config.resources.msGraphPhoto.scopes,
      { responseType: 'arraybuffer' }
    )
    if (!(response && response.data)) {
      return ''
    }
    const imageBase64 = new Buffer(response.data, 'binary').toString('base64')
    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    return `data:${response.headers['content-type']};base64, ${imageBase64}`
  } catch (error) {
    throw new Error(`Failed retrieving the graph photo: ${error}`)
  }
}
like image 790
DarkLite1 Avatar asked Sep 07 '25 15:09

DarkLite1


1 Answers

TypeScript doesn't generate warnings, only errors. As far as TS is concerned, that any assignment is valid. This is where the linter comes in to offer additional support.

Luckily you don't need to duplicate your interface. Use TypeScript's ReturnType to get the type of the profile object in your defaultState method:

type IProfile = ReturnType<typeof defaultState>["profile"]

The above line utilizes 3 great TypeScript features:

  • ReturnType to infer the type that a function returns
  • typeof to infer the interface from an object instance
  • ["profile"] to get the type of a certain property of an interface

Now, make your callGraph function generic:

function callGraph<T>(url: string, token: string, axiosConfig?: AxiosRequestConfig) {
  const params: AxiosRequestConfig = {
    method: 'GET',
    url: url,
    headers: { Authorization: `Bearer ${token}` },
  }
  return axios.request<T>({ ...params, ...axiosConfig })
}

And update the callGraph call in your getGraphDetails function:

...
  if (response && response.accessToken) {
    return callGraph<IProfile>(uri, response.accessToken, axiosConfig)
  }
...

Now your graph calls are properly typed, and you didn't have to duplicate your profile definition; rather you used TypeScript's awesome type inference technique to "read your interface" from the return type of your function.

like image 191
Arash Motamedi Avatar answered Sep 10 '25 07:09

Arash Motamedi