@@ -41,23 +41,60 @@ type GetFieldType<T, P> = P extends `${infer Left}.${infer Right}`
4141
4242type PropertyName = string | number | symbol ;
4343
44- const rePropName = RegExp (
45- // Match anything that isn't a dot or bracket.
46- '[^.[\\]]+' +
47- '|' +
48- // Or match property names within brackets.
49- '\\[(?:' +
50- // Match a non-string expression.
51- '([^"\'][^[]*)' +
52- '|' +
53- // Or match strings (supports escaping characters).
54- '(["\'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2' +
55- ')\\]' +
56- '|' +
57- // Or match "" as the space between consecutive dots or empty brackets.
58- '(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))' ,
59- 'g' ,
60- ) ;
44+ const reIsDeepProp = / \. | \[ (?: [ ^ [ \] ] * | ( [ " ' ] ) (?: (? ! \1) [ ^ \\ ] | \\ .) * ?\1) \] / ;
45+ const reIsPlainProp = / ^ \w * $ / ;
46+ const rePropName =
47+ / [ ^ . [ \] ] + | \[ (?: ( - ? \d + (?: \. \d + ) ? ) | ( [ " ' ] ) ( (?: (? ! \2) [ ^ \\ ] | \\ .) * ?) \2) \] | (? = (?: \. | \[ \] ) (?: \. | \[ \] | $ ) ) / g;
48+ const reEscapeChar = / \\ ( \\ ) ? / g;
49+
50+ /**
51+ * Checks if `value` is a property name and not a property path.
52+ *
53+ * @private
54+ * @param {* } value The value to check.
55+ * @param {Object } [object] The object to query keys on.
56+ * @returns {boolean } Returns `true` if `value` is a property name, else `false`.
57+ */
58+ function isKey < TObject extends object > ( value : any , object : TObject ) : boolean {
59+ if ( Array . isArray ( value ) ) {
60+ return false ;
61+ }
62+ const type = typeof value ;
63+ if (
64+ type == 'number' ||
65+ type == 'symbol' ||
66+ type == 'boolean' ||
67+ value == null
68+ ) {
69+ return true ;
70+ }
71+ return (
72+ reIsPlainProp . test ( value ) ||
73+ ! reIsDeepProp . test ( value ) ||
74+ ( object != null && value in Object ( object ) )
75+ ) ;
76+ }
77+
78+ /**
79+ * Converts `string` to a property path array.
80+ *
81+ * @private
82+ * @param {string } string The string to convert.
83+ * @returns {Array } Returns the property path array.
84+ */
85+ function stringToPath ( string : string ) : string [ ] {
86+ const result = [ ] ;
87+ if ( string . charCodeAt ( 0 ) === 46 /* . */ ) {
88+ result . push ( '' ) ;
89+ }
90+ string . replace ( rePropName , ( match , number , quote , subString ) => {
91+ result . push (
92+ quote ? subString . replace ( reEscapeChar , '$1' ) : number || match ,
93+ ) ;
94+ return match ;
95+ } ) ;
96+ return result ;
97+ }
6198
6299/**
63100 * Casts `value` to a path array if it's not one.
@@ -75,13 +112,14 @@ function castPath<TPath extends string, TObject>(
75112 path : TPath ,
76113 obj : TObject ,
77114) : Exclude < GetFieldType < TObject , TPath > , null | undefined > ;
78- function castPath ( value : string ) : string [ ] {
79- const result : string [ ] = [ ] ;
80- let match : RegExpExecArray | null ;
81- while ( ( match = rePropName . exec ( value ) ) ) {
82- result . push ( match [ 3 ] ?? match [ 1 ] ?. trim ( ) ?? match [ 0 ] ) ;
115+ function castPath < TObject extends object > (
116+ value : string ,
117+ object : TObject ,
118+ ) : string [ ] {
119+ if ( Array . isArray ( value ) ) {
120+ return value ;
83121 }
84- return result ;
122+ return isKey ( value , object ) ? [ value ] : stringToPath ( String ( value ) ) ;
85123}
86124
87125export function getProp < TObject extends object , TKey extends keyof TObject > (
0 commit comments