Specify types for convert-case and html-escape
Test plan: - Existing tests pass flag=none Change-Id: Ib4ff7609f4ca71242cef7ed91de53a5ef177112c Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/310891 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Kai Bjorkman <kbjorkman@instructure.com> Reviewed-by: Derek Williams <derek.williams@instructure.com> QA-Review: Cameron Ray <cameron.ray@instructure.com> Product-Review: Cameron Ray <cameron.ray@instructure.com> Build-Review: Andrea Cirulli <andrea.cirulli@instructure.com>
This commit is contained in:
parent
01b718aa98
commit
419a4906e2
|
@ -61,6 +61,8 @@ module.exports = {
|
|||
path.resolve(canvasDir, 'packages/jquery-sticky'),
|
||||
path.resolve(canvasDir, 'packages/mathml'),
|
||||
path.resolve(canvasDir, 'packages/defer-promise'),
|
||||
path.resolve(canvasDir, 'packages/convert-case'),
|
||||
path.resolve(canvasDir, 'packages/html-escape'),
|
||||
path.resolve(canvasDir, 'packages/persistent-array'),
|
||||
path.resolve(canvasDir, 'packages/slickgrid'),
|
||||
path.resolve(canvasDir, 'packages/with-breakpoints'),
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
function camelizeString(str, lowerFirst) {
|
||||
function camelizeString(str: string, lowerFirst: boolean = false) {
|
||||
return (str || '').replace(/(?:^|[-_])(\w)/g, (_, c, index) => {
|
||||
if (index === 0 && lowerFirst) {
|
||||
return c ? c.toLowerCase() : ''
|
||||
|
@ -26,16 +26,17 @@ function camelizeString(str, lowerFirst) {
|
|||
})
|
||||
}
|
||||
|
||||
function underscoreString(str) {
|
||||
function underscoreString(str: string) {
|
||||
return str.replace(/([A-Z])/g, $1 => `_${$1.toLowerCase()}`)
|
||||
}
|
||||
|
||||
// Convert all property keys in an object to camelCase
|
||||
export function camelize(props) {
|
||||
let prop
|
||||
const attrs = {}
|
||||
export function camelize(props: {[key: string]: any}) {
|
||||
const attrs: {
|
||||
[key: string]: any
|
||||
} = {}
|
||||
|
||||
for (prop in props) {
|
||||
for (const prop in props) {
|
||||
if (props.hasOwnProperty(prop)) {
|
||||
attrs[camelizeString(prop, true)] = props[prop]
|
||||
}
|
||||
|
@ -44,9 +45,11 @@ export function camelize(props) {
|
|||
return attrs
|
||||
}
|
||||
|
||||
export function underscore(props) {
|
||||
export function underscore(props: {[key: string]: any}) {
|
||||
let prop
|
||||
const attrs = {}
|
||||
const attrs: {
|
||||
[key: string]: any
|
||||
} = {}
|
||||
|
||||
for (prop in props) {
|
||||
if (props.hasOwnProperty(prop)) {
|
|
@ -3,5 +3,10 @@
|
|||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"author": "neme",
|
||||
"main": "./index.js"
|
||||
"main": "./index.ts",
|
||||
"babel": {
|
||||
"presets": [
|
||||
"@babel/preset-typescript"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,13 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import INST from 'browser-sniffer'
|
||||
|
||||
class SafeString {
|
||||
constructor(string) {
|
||||
'string': string
|
||||
|
||||
constructor(string: any) {
|
||||
this.string = typeof string === 'string' ? string : `${string}`
|
||||
}
|
||||
|
||||
|
@ -36,10 +39,10 @@ const ENTITIES = {
|
|||
"'": ''',
|
||||
'/': '/',
|
||||
'`': '`', // for old versions of IE
|
||||
'=': '=' // in case of unquoted attributes
|
||||
}
|
||||
'=': '=', // in case of unquoted attributes
|
||||
} as const
|
||||
|
||||
function htmlEscape(str) {
|
||||
export function htmlEscape(str: string): string {
|
||||
// ideally we should wrap this in a SafeString, but this is how it has
|
||||
// always worked :-/
|
||||
return str.replace(/[&<>"'\/`=]/g, c => ENTITIES[c])
|
||||
|
@ -47,7 +50,7 @@ function htmlEscape(str) {
|
|||
|
||||
// Escapes HTML tags from string, or object string props of `strOrObject`.
|
||||
// returns the new string, or the object with escaped properties
|
||||
export default function escape(strOrObject) {
|
||||
export default function escape<T>(strOrObject: string | SafeString | Object) {
|
||||
if (typeof strOrObject === 'string') {
|
||||
return htmlEscape(strOrObject)
|
||||
} else if (strOrObject instanceof SafeString) {
|
||||
|
@ -62,7 +65,7 @@ export default function escape(strOrObject) {
|
|||
strOrObject[k] = escape(v)
|
||||
}
|
||||
}
|
||||
return strOrObject
|
||||
return strOrObject as T
|
||||
}
|
||||
escape.SafeString = SafeString
|
||||
|
||||
|
@ -78,7 +81,7 @@ const UNESCAPE_ENTITIES = Object.keys(ENTITIES).reduce((map, key) => {
|
|||
const unescapeSource = `(?:${Object.keys(UNESCAPE_ENTITIES).join('|')})`
|
||||
const UNESCAPE_REGEX = new RegExp(unescapeSource, 'g')
|
||||
|
||||
function unescape(str) {
|
||||
function unescape(str: string) {
|
||||
return str.replace(UNESCAPE_REGEX, match => UNESCAPE_ENTITIES[match])
|
||||
}
|
||||
|
|
@ -3,5 +3,10 @@
|
|||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"author": "neme",
|
||||
"main": "./index.js"
|
||||
"main": "./index.ts",
|
||||
"babel": {
|
||||
"presets": [
|
||||
"@babel/preset-typescript"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,8 @@ module.exports = {
|
|||
path.join(canvasDir, 'packages/jquery-selectmenu'),
|
||||
path.join(canvasDir, 'packages/mathml'),
|
||||
path.join(canvasDir, 'packages/defer-promise'),
|
||||
path.resolve(canvasDir, 'packages/convert-case'),
|
||||
path.resolve(canvasDir, 'packages/html-escape'),
|
||||
path.join(canvasDir, 'packages/persistent-array'),
|
||||
path.join(canvasDir, 'packages/slickgrid'),
|
||||
path.join(canvasDir, 'packages/with-breakpoints'),
|
||||
|
|
|
@ -263,6 +263,8 @@ module.exports = {
|
|||
path.resolve(canvasDir, 'packages/jquery-sticky'),
|
||||
path.resolve(canvasDir, 'packages/mathml'),
|
||||
path.resolve(canvasDir, 'packages/defer-promise'),
|
||||
path.resolve(canvasDir, 'packages/convert-case'),
|
||||
path.resolve(canvasDir, 'packages/html-escape'),
|
||||
path.resolve(canvasDir, 'packages/persistent-array'),
|
||||
path.resolve(canvasDir, 'packages/slickgrid'),
|
||||
path.resolve(canvasDir, 'packages/with-breakpoints'),
|
||||
|
|
|
@ -63,18 +63,18 @@ export type Student = Readonly<{
|
|||
avatar_url?: string
|
||||
created_at: string
|
||||
email: null | string
|
||||
first_name: string
|
||||
group_ids: string[]
|
||||
id: string
|
||||
integration_id: null | string
|
||||
last_name: string
|
||||
login_id: string
|
||||
name: string
|
||||
short_name: string
|
||||
sis_import_id: null | string
|
||||
sis_user_id: null | string
|
||||
}> & {
|
||||
enrollments: Enrollment[]
|
||||
first_name: string
|
||||
last_name: string
|
||||
name: string
|
||||
} & Partial<{
|
||||
computed_current_score: number
|
||||
computed_final_score: number
|
||||
|
|
|
@ -44,6 +44,7 @@ import type {
|
|||
GradingPeriod,
|
||||
Module,
|
||||
Section,
|
||||
Student,
|
||||
StudentGroup,
|
||||
StudentGroupCategory,
|
||||
StudentGroupCategoryMap,
|
||||
|
@ -490,14 +491,14 @@ export function maxAssignmentCount(
|
|||
}
|
||||
|
||||
// mutative
|
||||
export function escapeStudentContent(student) {
|
||||
export function escapeStudentContent(student: Student) {
|
||||
const unescapedName = student.name
|
||||
const unescapedSortableName = student.sortable_name
|
||||
const unescapedFirstName = student.first_name
|
||||
const unescapedLastName = student.last_name
|
||||
|
||||
// TODO: selectively escape fields
|
||||
const escapedStudent = htmlEscape(student)
|
||||
const escapedStudent = htmlEscape<Student>(student)
|
||||
escapedStudent.name = unescapedName
|
||||
escapedStudent.sortable_name = unescapedSortableName
|
||||
escapedStudent.first_name = unescapedFirstName
|
||||
|
|
|
@ -84,5 +84,7 @@ export function updateCourseSettings(
|
|||
}
|
||||
) {
|
||||
const url = `/api/v1/courses/${courseId}/settings`
|
||||
return axios.put(url, underscore(settings)).then(response => ({data: camelize(response.data)}))
|
||||
return axios.put(url, underscore(settings)).then(response => ({
|
||||
data: camelize<{allowFinalGradeOverride: boolean}>(response.data),
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ export type SubmissionTrayProps = {
|
|||
isOpen: boolean
|
||||
isFirstStudent: boolean
|
||||
isLastStudent: boolean
|
||||
latePolicy: LatePolicyCamelized
|
||||
latePolicy?: LatePolicyCamelized
|
||||
locale: string
|
||||
editSubmissionComment: (commentId: string | null) => void
|
||||
onClose: () => void
|
||||
|
|
|
@ -213,7 +213,7 @@ export type CourseContent = {
|
|||
gradingSchemes: GradingScheme[]
|
||||
gradingPeriodAssignments: GradingPeriodAssignmentMap
|
||||
assignmentStudentVisibility: {[assignmentId: string]: null | StudentMap}
|
||||
latePolicy: LatePolicyCamelized
|
||||
latePolicy?: LatePolicyCamelized
|
||||
students: StudentDatastore
|
||||
modulesById: ModuleMap
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import type {
|
|||
GradebookOptions,
|
||||
GradebookSettings,
|
||||
InitialActionStates,
|
||||
LatePolicyCamelized,
|
||||
} from './gradebook.d'
|
||||
import type {GridDisplaySettings, FilterColumnsOptions} from './grid.d'
|
||||
import {camelize} from 'convert-case'
|
||||
|
@ -132,7 +133,9 @@ export function getInitialCourseContent(options: GradebookOptions): CourseConten
|
|||
gradingSchemes: options.grading_schemes.map(camelize),
|
||||
gradingPeriodAssignments: {},
|
||||
assignmentStudentVisibility: {},
|
||||
latePolicy: options.late_policy ? camelize(options.late_policy) : undefined,
|
||||
latePolicy: options.late_policy
|
||||
? camelize<LatePolicyCamelized>(options.late_policy)
|
||||
: undefined,
|
||||
students: new StudentDatastore({}, {}),
|
||||
modulesById: {},
|
||||
}
|
||||
|
|
|
@ -136,7 +136,7 @@ export const configureRecognition = (recognition, messages) => {
|
|||
}
|
||||
|
||||
// xsslint safeString.function linebreak
|
||||
function linebreak(transcript) {
|
||||
function linebreak(transcript: string) {
|
||||
return htmlEscape(transcript).replace(/\n\n/g, '<p></p>').replace(/\n/g, '<br>')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -209,3 +209,15 @@ declare module '@instructure/ui-badge' {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'convert-case' {
|
||||
export function camelize<T>(props: {[key: string]: unknown}): T
|
||||
export function underscore<T>(props: {[key: string]: unknown}): T
|
||||
}
|
||||
|
||||
declare module 'html-escape' {
|
||||
type Escapeable = string | number | {[key: string]: Escapeable}
|
||||
export function escape<T>(strOrObject: Escapeable): T
|
||||
export function htmlEscape(str: string): string
|
||||
export function unescape(str: string): string
|
||||
}
|
||||
|
|
|
@ -18,7 +18,11 @@
|
|||
|
||||
import {camelize, underscore} from 'convert-case'
|
||||
import {originalityReportSubmissionKey} from './originalityReportHelper'
|
||||
import type {PlagiarismData, SimilarityEntry} from '@canvas/grading/grading.d'
|
||||
import type {
|
||||
PlagiarismData,
|
||||
SimilarityEntry,
|
||||
CamelizedSubmissionWithOriginalityReport,
|
||||
} from '@canvas/grading/grading.d'
|
||||
|
||||
export function isGraded(submission) {
|
||||
// TODO: remove when we no longer camelize data in Gradebook
|
||||
|
@ -52,9 +56,9 @@ export function isHideable(submission) {
|
|||
// - "pending" reports (reports still being processed)
|
||||
// - scored reports, with higher scores (indicating more likely plagiarism) first
|
||||
export function extractSimilarityInfo(submission) {
|
||||
const sub = camelize(submission)
|
||||
const sub = camelize(submission) as CamelizedSubmissionWithOriginalityReport
|
||||
let plagiarismData
|
||||
let type
|
||||
let type: 'vericite' | 'turnitin' | 'originality_report' | null = null
|
||||
|
||||
if (sub.vericiteData?.provider === 'vericite') {
|
||||
type = 'vericite'
|
||||
|
|
Loading…
Reference in New Issue