Revision f24f74eb8908ceb955b013547908dc83b0231772 authored by Babak K. Shandiz on 16 August 2022, 16:27:29 UTC, committed by GitHub on 16 August 2022, 16:27:29 UTC
* ⚗️ Add test to verify code fix works when implementing a mapped type with indirect keyof

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* 🔨 Add property as implementation for symbols that has no declaration

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts
index 94b64e57..a4c11fd5 100644
--- a/src/services/codefixes/helpers.ts
+++ b/src/services/codefixes/helpers.ts
@@ -60,21 +60,19 @@ namespace ts.codefix {
         isAmbient = false,
     ): void {
         const declarations = symbol.getDeclarations();
-        if (!(declarations && declarations.length)) {
-            return undefined;
-        }
+        const declaration = declarations ? declarations[0] : undefined;
         const checker = context.program.getTypeChecker();
         const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions());
-        const declaration = declarations[0];
+        const kind = declaration?.kind ?? SyntaxKind.PropertySignature;
         const name = getSynthesizedDeepClone(getNameOfDeclaration(declaration), /*includeTrivia*/ false) as PropertyName;
-        const visibilityModifier = createVisibilityModifier(getEffectiveModifierFlags(declaration));
+        const visibilityModifier = createVisibilityModifier(declaration ? getEffectiveModifierFlags(declaration) : ModifierFlags.None);
         const modifiers = visibilityModifier ? factory.createNodeArray([visibilityModifier]) : undefined;
         const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration));
         const optional = !!(symbol.flags & SymbolFlags.Optional);
         const ambient = !!(enclosingDeclaration.flags & NodeFlags.Ambient) || isAmbient;
         const quotePreference = getQuotePreference(sourceFile, preferences);

-        switch (declaration.kind) {
+        switch (kind) {
             case SyntaxKind.PropertySignature:
             case SyntaxKind.PropertyDeclaration:
                 const flags = quotePreference === QuotePreference.Single ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : undefined;
@@ -88,13 +86,16 @@ namespace ts.codefix {
                 }
                 addClassElement(factory.createPropertyDeclaration(
                     modifiers,
-                    name,
+                    declaration ? name : symbol.getName(),
                     optional && (preserveOptional & PreserveOptionalFlags.Property) ? factory.createToken(SyntaxKind.QuestionToken) : undefined,
                     typeNode,
                     /*initializer*/ undefined));
                 break;
             case SyntaxKind.GetAccessor:
             case SyntaxKind.SetAccessor: {
+                if (!declarations) {
+                    break;
+                }
                 let typeNode = checker.typeToTypeNode(type, enclosingDeclaration, /*flags*/ undefined, getNoopSymbolTrackerWithResolver(context));
                 const allAccessors = getAllAccessorDeclarations(declarations, declaration as AccessorDeclaration);
                 const orderedAccessors = allAccessors.secondAccessor
@@ -138,6 +139,10 @@ namespace ts.codefix {
                 // If there is more than one overload but no implementation signature
                 // (eg: an abstract method or interface declaration), there is a 1-1
                 // correspondence of declarations and signatures.
+                if (!declarations) {
+                    break;
+                }
+
                 const signatures = checker.getSignaturesOfType(type, SignatureKind.Call);
                 if (!some(signatures)) {
                     break;

* 🔨 Improve code readability

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts
index 2f5c8703ab..aea0206a8a 100644
--- a/src/services/codefixes/helpers.ts
+++ b/src/services/codefixes/helpers.ts
@@ -60,7 +60,7 @@ namespace ts.codefix {
         isAmbient = false,
     ): void {
         const declarations = symbol.getDeclarations();
-        const declaration = declarations ? declarations[0] : undefined;
+        const declaration = declarations?.[0];
         const checker = context.program.getTypeChecker();
         const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions());
         const kind = declaration?.kind ?? SyntaxKind.PropertySignature;
@@ -93,9 +93,7 @@ namespace ts.codefix {
                 break;
             case SyntaxKind.GetAccessor:
             case SyntaxKind.SetAccessor: {
-                if (!declarations) {
-                    break;
-                }
+                Debug.assertIsDefined(declarations);
                 let typeNode = checker.typeToTypeNode(type, enclosingDeclaration, /*flags*/ undefined, getNoopSymbolTrackerWithResolver(context));
                 const allAccessors = getAllAccessorDeclarations(declarations, declaration as AccessorDeclaration);
                 const orderedAccessors = allAccessors.secondAccessor
@@ -139,10 +137,7 @@ namespace ts.codefix {
                 // If there is more than one overload but no implementation signature
                 // (eg: an abstract method or interface declaration), there is a 1-1
                 // correspondence of declarations and signatures.
-                if (!declarations) {
-                    break;
-                }
-
+                Debug.assertIsDefined(declarations);
                 const signatures = type.isUnion() ? flatMap(type.types, t => t.getCallSignatures()) : type.getCallSignatures();
                 if (!some(signatures)) {
                     break;

* 📜 Add comment regarding mapped type children's  missing declaration

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>
Co-authored-by: Andrew Branch <andrew@wheream.io>
1 parent 90fa1c7
Raw File
vpathUtil.ts
namespace vpath {
    export import sep = ts.directorySeparator;
    export import normalizeSeparators = ts.normalizeSlashes;
    export import isAbsolute = ts.isRootedDiskPath;
    export import isRoot = ts.isDiskPathRoot;
    export import hasTrailingSeparator = ts.hasTrailingDirectorySeparator;
    export import addTrailingSeparator = ts.ensureTrailingDirectorySeparator;
    export import removeTrailingSeparator = ts.removeTrailingDirectorySeparator;
    export import normalize = ts.normalizePath;
    export import combine = ts.combinePaths;
    export import parse = ts.getPathComponents;
    export import reduce = ts.reducePathComponents;
    export import format = ts.getPathFromPathComponents;
    export import resolve = ts.resolvePath;
    export import compare = ts.comparePaths;
    export import compareCaseSensitive = ts.comparePathsCaseSensitive;
    export import compareCaseInsensitive = ts.comparePathsCaseInsensitive;
    export import dirname = ts.getDirectoryPath;
    export import basename = ts.getBaseFileName;
    export import extname = ts.getAnyExtensionFromPath;
    export import relative = ts.getRelativePathFromDirectory;
    export import beneath = ts.containsPath;
    export import changeExtension = ts.changeAnyExtension;
    export import isTypeScript = ts.hasTSFileExtension;
    export import isJavaScript = ts.hasJSFileExtension;

    const invalidRootComponentRegExp = /^(?!(\/|\/\/\w+\/|[a-zA-Z]:\/?|)$)/;
    const invalidNavigableComponentRegExp = /[:*?"<>|]/;
    const invalidNavigableComponentWithWildcardsRegExp = /[:"<>|]/;
    const invalidNonNavigableComponentRegExp = /^\.{1,2}$|[:*?"<>|]/;
    const invalidNonNavigableComponentWithWildcardsRegExp = /^\.{1,2}$|[:"<>|]/;
    const extRegExp = /\.\w+$/;

    export const enum ValidationFlags {
        None = 0,

        RequireRoot = 1 << 0,
        RequireDirname = 1 << 1,
        RequireBasename = 1 << 2,
        RequireExtname = 1 << 3,
        RequireTrailingSeparator = 1 << 4,

        AllowRoot = 1 << 5,
        AllowDirname = 1 << 6,
        AllowBasename = 1 << 7,
        AllowExtname = 1 << 8,
        AllowTrailingSeparator = 1 << 9,
        AllowNavigation = 1 << 10,
        AllowWildcard = 1 << 11,

        /** Path must be a valid directory root */
        Root = RequireRoot | AllowRoot | AllowTrailingSeparator,

        /** Path must be a absolute */
        Absolute = RequireRoot | AllowRoot | AllowDirname | AllowBasename | AllowExtname | AllowTrailingSeparator | AllowNavigation,

        /** Path may be relative or absolute */
        RelativeOrAbsolute = AllowRoot | AllowDirname | AllowBasename | AllowExtname | AllowTrailingSeparator | AllowNavigation,

        /** Path may only be a filename */
        Basename = RequireBasename | AllowExtname,
    }

    function validateComponents(components: string[], flags: ValidationFlags, hasTrailingSeparator: boolean) {
        const hasRoot = !!components[0];
        const hasDirname = components.length > 2;
        const hasBasename = components.length > 1;
        const hasExtname = hasBasename && extRegExp.test(components[components.length - 1]);
        const invalidComponentRegExp = flags & ValidationFlags.AllowNavigation
            ? flags & ValidationFlags.AllowWildcard ? invalidNavigableComponentWithWildcardsRegExp : invalidNavigableComponentRegExp
            : flags & ValidationFlags.AllowWildcard ? invalidNonNavigableComponentWithWildcardsRegExp : invalidNonNavigableComponentRegExp;

        // Validate required components
        if (flags & ValidationFlags.RequireRoot && !hasRoot) return false;
        if (flags & ValidationFlags.RequireDirname && !hasDirname) return false;
        if (flags & ValidationFlags.RequireBasename && !hasBasename) return false;
        if (flags & ValidationFlags.RequireExtname && !hasExtname) return false;
        if (flags & ValidationFlags.RequireTrailingSeparator && !hasTrailingSeparator) return false;

        // Required components indicate allowed components
        if (flags & ValidationFlags.RequireRoot) flags |= ValidationFlags.AllowRoot;
        if (flags & ValidationFlags.RequireDirname) flags |= ValidationFlags.AllowDirname;
        if (flags & ValidationFlags.RequireBasename) flags |= ValidationFlags.AllowBasename;
        if (flags & ValidationFlags.RequireExtname) flags |= ValidationFlags.AllowExtname;
        if (flags & ValidationFlags.RequireTrailingSeparator) flags |= ValidationFlags.AllowTrailingSeparator;

        // Validate disallowed components
        if (~flags & ValidationFlags.AllowRoot && hasRoot) return false;
        if (~flags & ValidationFlags.AllowDirname && hasDirname) return false;
        if (~flags & ValidationFlags.AllowBasename && hasBasename) return false;
        if (~flags & ValidationFlags.AllowExtname && hasExtname) return false;
        if (~flags & ValidationFlags.AllowTrailingSeparator && hasTrailingSeparator) return false;

        // Validate component strings
        if (invalidRootComponentRegExp.test(components[0])) return false;
        for (let i = 1; i < components.length; i++) {
            if (invalidComponentRegExp.test(components[i])) return false;
        }

        return true;
    }

    export function validate(path: string, flags: ValidationFlags = ValidationFlags.RelativeOrAbsolute) {
        const components = parse(path);
        const trailing = hasTrailingSeparator(path);
        if (!validateComponents(components, flags, trailing)) throw vfs.createIOError("ENOENT");
        return components.length > 1 && trailing ? format(reduce(components)) + sep : format(reduce(components));
    }

    export function isDeclaration(path: string) {
        return ts.isDeclarationFileName(path);
    }

    export function isSourceMap(path: string) {
        return extname(path, ".map", /*ignoreCase*/ false).length > 0;
    }

    const javaScriptSourceMapExtensions: readonly string[] = [".js.map", ".jsx.map"];

    export function isJavaScriptSourceMap(path: string) {
        return extname(path, javaScriptSourceMapExtensions, /*ignoreCase*/ false).length > 0;
    }

    export function isJson(path: string) {
        return extname(path, ".json", /*ignoreCase*/ false).length > 0;
    }

    export function isDefaultLibrary(path: string) {
        return isDeclaration(path)
            && basename(path).startsWith("lib.");
    }

    export function isTsConfigFile(path: string): boolean {
        return path.indexOf("tsconfig") !== -1 && path.indexOf("json") !== -1;
    }
}
back to top